...
Binary analysis (Finding vulnerabilities)
Struct
Code Block |
---|
typedef struct ORDER{
char orderCode[8];
unsigned int orderNumber;
char orderCandyName[8];
int candyCode;
};
typedef struct CANDIES {
char candyName[8];
unsigned int orderNumber;
int candyCode;
};
|
Main
- 해당 함수는 다음과 같은 기능을 합니다.
- 해당는 함수는 setCandy() 함수를 호출하여 Candy 정보를 설정합니다.
- 해당는 함수는 login() 함수를 호출하여 사용하여 계정정보를 확인합니다.
- 해당는 함수는 사용자가 login에 실패 할 경우 다음과 같이 동작합니다.
- 해당 함수는 login이 실패시 사용자에게 계정을 생성 할 것인지 묻습니다.
해당 함수는 addAccount() 함수를 이용해 새로운 계정을 생성합니다.
- 그리고 해당 함수는 login을 3번 실패하면 프로그램은 종료됩니다.
- 해당 함수는 login이 실패시 사용자에게 계정을 생성 할 것인지 묻습니다.
- 해당는 함수는 사용자가 login에 성공하면 아래 기능들을 이용할 수 있습니다.
- 제고 출력, 주문, 충전, 로그아웃
- gLoginAccount→state 의 값이 1인 경우 "orderMenu", "Account" 기능을 이용 할 수 있습니다.
...
- 해당 함수는 다음과 같은 기능을 합니다.
- 해당 함수는 사용자로 부터 ID,Password를 입력 받습니다.
- 해당 함수는 입력 받은 값을 전역 변수 gAccount[]에 존재하는지 확인합니다.
- 해당 함수는 인증에 성공하면, 해당 계정 정보가 저장된 gAccount[]의 주소를 전역변수 "gLoginAccount" 에 저장합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
signed __int64 login() { size_t lenUserInputID; // rbx size_t lenID; // rax size_t lenUserInputPW; // rbx size_t lenPW; // rax signed int i; // [rsp+Ch] [rbp-34h] char id[8]; // [rsp+10h] [rbp-30h] char pw[8]; // [rsp+20h] [rbp-20h] unsigned __int64 v8; // [rsp+28h] [rbp-18h] v8 = __readfsqword(0x28u); memset(id, 0, 8uLL); memset(pw, 0, 8uLL); printf("\nEnter your ID.\n> ", 0LL); UserInput(id, 8LL); printf("Enter your Password.\n> ", 8LL); UserInput(pw, 8LL); for ( i = 0; i <= 2; ++i ) { if ( gAccount[i].state ) { lenUserInputID = strlen(id); if ( lenUserInputID == strlen(gAccount[i].fd->id) ) { lenID = strlen(gAccount[i].fd->id); if ( !strncmp(gAccount[i].fd->id, id, lenID) ) { lenUserInputPW = strlen(pw); if ( lenUserInputPW == strlen(gAccount[i].fd->pw) ) { lenPW = strlen(gAccount[i].fd->pw); if ( !strncmp(gAccount[i].fd->pw, pw, lenPW) ) { gLoginAccount = (struct ACCOUNT *)(32LL * i + 0x604220); printf("\nHi, %s", gAccount[i].fd->id); return 1LL; } } } } } } return 0LL; } |
...
- 해당 함수는 gLoginAccount->state의 값이 1인 경우 사용가능합니다.
- 해당 함수는 다음과 같은 기능을 합니다.
- 해당 함수는 사용 가능한 기능 목록을 출력합니다.
- 계정 삭제, 비밀번호 변경
- 해당 함수는 사용자로 부터 사용할 기능의 번호를 입력 받아 해당 기능을 호출합니다.
- 해당 함수는 사용 가능한 기능 목록을 출력합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned __int64 Account() { int tmp; // eax signed int i; // [rsp+8h] [rbp-58h] signed int control; // [rsp+Ch] [rbp-54h] char funcList[3][22]; // [rsp+10h] [rbp-50h] unsigned __int64 v5; // [rsp+58h] [rbp-8h] v5 = __readfsqword(0x28u); control = 1; strcpy((char *)funcList, "Delete account"); *(_DWORD *)&funcList[0][16] = 0; *(_WORD *)&funcList[0][20] = 0; strcpy(funcList[1], "Change password"); *(_DWORD *)&funcList[1][16] = 0; *(_WORD *)&funcList[1][20] = 0; *(_OWORD *)&funcList[2][0] = (unsigned __int64)'tixE'; *(_DWORD *)&funcList[2][16] = 0; *(_WORD *)&funcList[2][20] = 0; while ( control ) { puts("\nAccount."); for ( i = 0; i <= 2; ++i ) printf("%d) %s\n", (unsigned int)(i + 1), funcList[i]); printf("Command : "); tmp = retNumber(2LL); switch ( tmp ) { case 2: changePW(); break; case 3: control = 0; break; case 1: delAccount(); break; } } return __readfsqword(0x28u) ^ v5; } |
...
- 해당 함수는 gLoginAccount->state의 값이 1인 경우 사용가능합니다.
- 해당 함수는 다음과 같은 기능을 합니다.
- 해당 함수는 사용 가능한 기능 목록을 출력합니다.
- 주문 목록, 주문 목록 추가, 주문 목록 취소, 캔디 주문
- 해당 함수는 사용자로 부터 사용할 기능의 번호를 입력 받아 해당 기능을 호출합니다.
- 해당 함수는 사용 가능한 기능 목록을 출력합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned __int64 orderMenu() { signed int i; // [rsp+8h] [rbp-88h] unsigned int control; // [rsp+Ch] [rbp-84h] char funcList[5][22]; // [rsp+10h] [rbp-80h] unsigned __int64 v4; // [rsp+88h] [rbp-8h] v4 = __readfsqword(0x28u); control = 1; strcpy((char *)funcList, "Order List"); *(_DWORD *)&funcList[0][16] = 0; *(_WORD *)&funcList[0][20] = 0; strcpy(funcList[1], "Add to Order List"); *(_WORD *)&funcList[1][20] = 0; *(_QWORD *)&funcList[2][0] = 'o lecnaC'; *(_QWORD *)&funcList[2][8] = 'o s���en'; *(_DWORD *)&funcList[2][16] = 'redr'; *(_WORD *)&funcList[2][20] = '.'; strcpy(funcList[3], "Order candy"); *(_DWORD *)&funcList[3][16] = 0; *(_WORD *)&funcList[3][20] = 0; *(_OWORD *)&funcList[4][0] = (unsigned __int64)'tixE'; *(_DWORD *)&funcList[4][16] = 0; *(_WORD *)&funcList[4][20] = 0; LABEL_11: while ( control ) { puts("\nOrder candy."); for ( i = 0; i <= 4; ++i ) printf("%d) %s\n", (unsigned int)(i + 1), funcList[i]); printf("Command : "); switch ( (unsigned int)retNumber(2LL) ) { case 1u: orderList(); break; case 2u: addToOrderList(); break; case 3u: orderCancel(); break; case 4u: orderCandy(); break; case 5u: control = 0; break; default: goto LABEL_11; } } return __readfsqword(0x28u) ^ v4; } |
...
- 다음과 같은 구조체를 사용합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
typedef struct ORDER{ char orderCode[8]; unsigned int orderNumber; char orderCandyName[8]; int candyCode; }; typedef struct CANDIES { char candyName[8]; unsigned int orderNumber; int candyCode; }; |
...
해당 함수는 다음과 같은 기능을 합니다.
해당 함수는 checkCancel(i) 함수를 취소 가능한 주문을 확인하고, 주문 취소 가능한 목록을 출력합니다.
해당 함수는 취소 가능한 주문이 있으며, 사용자로 부터 주문 번호을 입력받습니다.
- 해당 함수는 입력 값을 검증한 후 reSort() 함수를 호출 해 주문을 취소합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned __int64 orderCancel() { unsigned int i; // [rsp+Ch] [rbp-44h] int j; // [rsp+Ch] [rbp-44h] int orderCnt; // [rsp+10h] [rbp-40h] unsigned int canNum; // [rsp+1Ch] [rbp-34h] int cancelableList[10]; // [rsp+20h] [rbp-30h] unsigned __int64 v6; // [rsp+48h] [rbp-8h] v6 = __readfsqword(0x28u); orderCnt = 0; if ( gOrderCnt ) { for ( i = 0; i < gOrderCnt; ++i ) { if ( (unsigned int)checkCancel(i) ) { cancelableList[orderCnt++] = i; printf("\n=*= Cancelable order (Order number : %d) =*=\n", i); printf("Order code: %s\n", gOrderList[i]); printf("Order count : %d\n", gOrderList[i]->orderNumber); printf("Order candy : %s\n", gOrderList[i]->orderCandyName); printf("Candy code: %d\n", (unsigned int)gOrderList[i]->candyCode); } } if ( orderCnt ) { canNum = retNumber(2LL); for ( j = 0; j < orderCnt; ++j ) { if ( cancelableList[j] == canNum ) reSort(canNum); } } } else { puts("You have never ordered a product."); } return __readfsqword(0x28u) ^ v6; } |
...
해당 함수는 다음과 같은 기능을 합니다.
해당 함수는 사용자로 부터 캔디 주문의 진행 여부를 확인합니다.
해당 함수는 getStockNum() 함수를 사용해 gOrderList[]에 저장된 값이 gStock[]에 존재하는지 확인합니다.
해당 함수는 gOrderList[]에 저장된 값 gStock[]에 존재 할 경우 다음과 같이 처리됩니다.
"gStock[]->candyNumber" 에 "gOrderList[]->orderNumber"의 값을 더하게 됩니다.
해당 함수는 gOrderList[]에 저장된 값 gStock[]에 존재하지 않을 경우 다음과 같이 처리됩니다.
해당 함수는 malloc() 함수를 사용하여 24 byte의 heap 영역을 할당합니다.
해당 함수는 해당 영역에 캔디에 대한 정보를 저장합니다.
사탕 이름, 가격, 캔디 정보가 저장되어 있는 주소 값
그리고 해당 함수는 124 byte의 heap 영역을 할당해서, 해당 영역에 사용자로 부터 입력받은 캔디 정보를 저장합니다.
해당 함수는 gOrderList[]에 저장된 값 gStock[]에 모두 저장한 후, gOrderList[] 영역을 모두 해제 합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned __int64 orderCandy() { struct STOCK *dest; // ST10_8 unsigned int i; // [rsp+4h] [rbp-1Ch] int num; // [rsp+Ch] [rbp-14h] unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); if ( gOrderCnt ) { orderList(); puts("\nWould you like to order these candies?"); puts("0) Yes, 1) No"); if ( !(unsigned int)retNumber(2LL) ) { for ( i = 0; i < gOrderCnt; ++i ) { num = getStockNum(i); if ( num ) { gStock[num - 1]->candyNumber += gOrderList[i]->orderNumber; } else if ( (unsigned int)gStockCnt > 4 ) { puts("The warehouse is full. Your new order can not be completed."); } else { puts("\nEnter information about newly added candy."); dest = (struct STOCK *)malloc(24uLL); strncpy(dest->candyName, gOrderList[i]->orderCandyName, 8uLL); dest->candyNumber = gOrderList[i]->orderNumber; printf("Enter the price of %s candy.\n", dest); dest->candyPrice = retNumber(5LL); printf("Enter a description of the %s candy.\n", dest); dest->candyDescription = (char *)malloc(124uLL); UserInput(dest->candyDescription, 124LL); gStock[gStockCnt++] = dest; } } while ( gOrderCnt ) { free(gOrderList[gOrderCnt - 1]); gOrderList[gOrderCnt-- - 1] = 0LL; } } } else { puts("You have never ordered a product."); } return __readfsqword(0x28u) ^ v4; } |
...
- 해당 프로그램에서 취약성을 이해하기 위해 ACCOUNT 구조체에 대한 이해가 필요합니다.
- 해당 구조체는 전역 변수로 선언되어 있습니다.
- 해당 프로그램은 3개의 ACCOUNT 구조체를 사용합니다.
- 첫번째 구조체에는 'Admin' 계정 정보가 저장되어 있습니다.
- 2,3번째 구조체는 사용자가 생성한 계정의 정보가 저장됩니다.
- 첫번째 구조체에는 'Admin' 계정 정보가 저장되어 있습니다.
- 해당 구조체는 전역 변수로 선언되어 있습니다.
Panel | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||||||||
|
...
다음과 같이 UAF 취약성을 사용해 "gAccount[1].fd→state" 값을 변경할 수 있습니다.
ACCOUNT 구조체의 크기는 128 byte 입니다.
orderCandy() 함수에 dest->candyDescription(사탕의 정보를 입력하는 영역)에 할당되는 크기는 124 byte입니다.
- 해제된 "gAccount[1].fd"영역을 "dest→candyDescription" 에 할당받아야 합니다.
- 해당 영역("dest→candyDescription")에 문자 8개 이상을 저장하면 "gAccount[1].fd→state" 영역을 덮어 쓸 수 있습니다.
- 주의 할 내용은 다음과 같습니다.
- UAF 공격시 제일 중요한 부분은 House of lore 공격을 위해 ACCOUNT 구조체와 같은 크기의 공간을 할당받고 해제 할 수 있어야 합니다.
- 즉, 해제된 "gAccount[1].fd" 영역에 반드시 "dest->candyDescription"에 할당되는 영역이 할당되어야 합니다.
- 즉, 해제된 "gAccount[1].fd" 영역에 반드시 "dest->candyDescription"에 할당되는 영역이 할당되어야 합니다.
- 그리고 아래와 같은 영역이 "gAccount[1].fd" 영역에 할당되지 않도록 주의 해야 합니다.
- 해당 프로그램은 사탕을 주문하기 위해서는 Order list에 구매할 사탕을 추가해야 합니다.
- Order list에 구매할 사탕을 추가 때 마다 Heap 영역(24 byte)을 할당 받습니다.
- 해당 프로그램은 주문한 사탕이 가게에 없는 제품이면 Heap 영역(24 byte)을 할당 받습니다.
- 해당 프로그램은 사탕을 주문하기 위해서는 Order list에 구매할 사탕을 추가해야 합니다.
- UAF 공격시 제일 중요한 부분은 House of lore 공격을 위해 ACCOUNT 구조체와 같은 크기의 공간을 할당받고 해제 할 수 있어야 합니다.
Structure of Exploit code
...