원래 HITCON Training Lab11 문제풀이를 정리하려고 했는데, 내 서버 환경의 GLIBC 버전이 2.31이어서 다 막혔다. 그래서 그냥 내가 이해할 수 있게 공격기법만 정리하고 나중에 실습할 수 있는 다른 문제를 풀어볼 예정이다.
Lab11
#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 ;
}
기능은 show, add, change, remove, exit 5가지가 있다. add, change, remove를 통해 item 구조체를 관리할 수 있다. 이때, box 구조체의 bamboo가 가장 처음으로 힙 메모리에 할당된다. 아래는 bamboo와 2개의 item 구조체를 add를 한 모습이다.
0x6032a0, a8에는 hello_message, goodbye_message의 함수 주소가 박히고 아래는 각각 item 구조체의 힙 구조다. hello_message 함수는 프로그램이 처음 시작할 때, 실행되고, 종료될 때는 bamboo->goodbye_message( )가 실행된다. 따라서 bamboo->goodbye_message( )가 있는 0x6032a8을 magic 함수로 덮어써서 문제를 해결하면 될 것 같다.
House of Force
위의 사진에서 0x6032f0을 보면 해당 주소가 Top chunk이다. 만약 프로그램에서 malloc을 하고 요청한 사이즈를 처리할 수 있는 bin list가 없다면, Top chunk의 공간을 사용한다. 이때, Top chunk의 size를 0xffffffffffffffff로 변조하고, 다음과 같은 크기로 malloc을 요청하면 원하는 주소에 값을 쓸 수 있다.
[원하는 주소] - [Chunk Header Size (0x10 or 0x8)] - [Top Chunk Addr] - [Chunk Header Size (0x10 or 0x8)]
https://www.lazenca.net/display/TEC/The House of Force
해당 문제에서는 add_item( )을 통해 할당하고, change_item( )을 이용하여 Top chunk의 size를 덮어쓸 수 있다. 탑청크의 사이즈까지는 변조했지만 다음 코드에 의해 막히고 다른 문제풀이를 보니깐 Unsafe Unlink로도 풀 수 있다고 해서 그 공격 기법도 알아봤다.
victim = av->top;
size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");
Unlink
Unlink는 이미 있는 bin list의 chunk가 double-linked list로 구성되어있는데, 이때 구조의 변화가 생기며 이를 수정해야할 때, fd와 bk를 변경하는 과정을 의미한다. (BK <--> P <--> FD에서 BK <--> FD로 수정하는 과정을 의미.)
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);
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;
if (!in_smallbin_range (chunksize_nomask (P))
//(https://www.lazenca.net/display/TEC/Unsafe+unlink)
코드를 보면, 첫 번째 if문에서 P의 size와 P의 다음 청크의 크기가 같은지 비교한다. 두 번째 if문에서는 FD->bk와 BK->fd가 P인지 비교한다. (FD->bk와 BK-fd가 해제할 포인터인지 비교)
Unsafe Unlink
Unsafe Unlink는 Unlink의 과정을 악용하여 공격하는 기법이다. 공격흐름은 다음과 같다.
- 2개의 chunk를 할당한다.
- 첫 번째 chunk 안에 Fake chunk를 생성한다. 이때, 두 번째 chunk의 prev_size와 size를 덮을 수 있어야 한다.
- Fake Chunk의 구조
- prev_size, size: 0x0
- fd: [원하는 주소] - 24, bk: [원하는 주소] - 16
- 두 번째 chunk의 prev_size 수정 , size: PREV_INUSE flag 제거 (fake chunk가 free 된 것으로 인식)
- 두 번째 chunk를 해제한다.
- free 과정을 진행하며, PRV_INUSE flag가 0이므로 이전 chunk가 free 된 것으로 인식하고 prev_size를 통해 fake chunk와 unlink 과정 수행
- 첫 번째 if문 우회
- chunksize(fake_chunk) = 0, prev_size(next_chunk(fake_chunk)) = 0
- 다음 chunk의 size를 구할 때, fake chunk의 size를 더해 다음 size를 구하는 걸로 기억하는데 0이므로 자기 자신을 가리키는 걸로 기억함.
- 두 번째 if문 우회
- 만약 원하는 주소를 0x1234라고 가정.
- FD = P->fd, BK = P->bk
- FD->bk = P->fd->bk, P->fd = 0x1234-24, 0x1234-24 -> bk이면 bk가 0x1234로부터 +24 위치에 있으므로 0x1234임.
- BK->fd = P->bk->fd, P->bk = 0x1234-16, 0x1234-16 -> fd이면 fd가 0x1234로부터 +16 위치에 있으므로 0x1234임.
- unlink 수행
- 0x1234 주소에 [0x1234 - 24] 값이 존재할 것이다.
- 이를 이용해서 공격을 수행
lab11 문제의 경우, itemlist에 size와 name 포인터가 저장된다. itemlist+8의 위치에는 인덱스 0의 name ptr이 저장된다. 위의 fake chunk를 구성할 때, fd와 bk를 [0x6020c8 - 24], [0x6020c8 - 16]으로 설정한다. (0x6020c8 = itemlist+8) 그리고 unlink가 진행된다면, itemlist+8의 위치에 0x6020b0(=0x6020c8 - 24)값이 저장된다. 그리고 change_item을 이용해 0번 인덱스를 수정하면 0x6020b0주소부터 수정할 수 있다. 원하는 함수 got로 itemlist+8을 덮어쓰고 공격을 진행하면 될 것 같다.
'Security' 카테고리의 다른 글
HITCON Training Lab13 (0) | 2021.01.02 |
---|---|
HITCON Training lab12 (0) | 2020.12.31 |
HITCON Training lab10 (first fit, uaf) (0) | 2020.12.22 |
HITCON Training lab7 ~ 9 (0) | 2020.11.21 |
BOF 중 scanf에 대한 글 (0) | 2020.11.13 |