Hi everyone, this is my first post to start a series of binary exploits on linux. My first article will focus on introducing the structure of the stack, buffer overflow errors and basic exploitation. From the following articles will gradually increase the difficulty, bypass anti-exploit methods on linux.
1. Stack and steps when a function call is made
This series I will not go into the stack, or the concept and functions of the registers, but this knowledge currently on google has a lot of articles, everyone can learn to read more before reading the article. mine. In this article, I introduce and exploit on 32 bit system.
First, when a function call is made, there are 3 steps to be taken:
- Put the function parameters on the stack in reverse order. For example
void func(int a, int b)
is placed on the stack in order b then a. - Put the EIP pointer on the stack, this is considered the return address so that when the function is done, the program will know the next instruction to execute.
- Call function is called (call function)
After the function is called, it is responsible for performing the following tasks in turn:
- Stores the current EBP register on the stack
- Save ESP to EBP
- Reduce EBP to make space for storing function local variables on the stack
=> This process is called function prolog
Example of function prolog: (assembly in intel structure)
1 2 3 4 5 | push ebp #lưu ebp vào stack mov ebp, esp #ebp = esp push ebx #không cần quan tâm sub esp, 0x194 #giảm giá trị của esp để tạo khoảng trống lưu trữ, vì stack từ low memory đến high memory, các bạn tham khảo hình minh họa ở dưới |
After the function is done, esp increments to ebp to clear the stack (delete the initial memory area created), the saved eip is removed to execute the next instruction.
=> this process is called function epilog Example:
1 2 3 | leave ret |
In it, the leave command has the following form:
1 2 3 | mov esp, ebp pop ebp |
The ret command is to get the eip value in the stack. Here is an example of a stack frame when making a function call func(int value1, int value2)
With the steps taken when the function call executes, and ends, the esp and ebp pointers are always returned to their original values and the program always knows the next instruction to execute after the function ends.
2. Debug with gdb (gef) and a simple buffer overflow exploit
I have a simple victim.c file as follows:
1 2 3 4 5 6 7 8 9 | #include <stdio.h> #include <string.h> int main(int argc, char *argv[]){ char array[64]; if(argc>1) strcpy(array, argv[1]); } |
After reading through the program, I realized that the program uses the strcpy function (copy string), a flaw of this function is that it does not specify the number of characters to copy, so if the array array contains at most 64 characters, that argv[1] is larger than 64 characters, the function will still execute, thereby causing an error in the stack.
After the main function call is called, the stack will look like this:
I proceed to compile the program using gcc with the following options:
gcc -m32 -z execstack -mpreferred-stack-boundary=2 -fno-stack-protector victim.c -o victim
In there:
- -m32: compile 32 bit program
- -z execstack: allows execution in the stack
- -mpreferred-stack-boundary=2 : Use DWORD size stack
- fno-stack-protector: disable stack canary
Here I have turned off all stack protection mechanisms, later in the series to bypass these mechanisms I will talk in more detail.
Debugging with gdb (gef) (github gef link: https://github.com/hugsy/gef )
Create breakpoint at main
1 2 3 | gef➤ b *main Breakpoint 1 at 0x118d |
Run the program with the argument: “test”:
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 | gef➤ r "test" Starting program: /home/kali/Desktop/Binary Exploit/victim "test" [*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc9000' [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, 0x5655618d in main () [ Legend: Modified register | Code | Heap | Stack | String ] ────────────────────────────────────────────────────────────────── registers ──── $eax : 0x5655618d → <main+0> push ebp $ebx : 0xf7e1eff4 → 0x0021ed8c $ecx : 0xcce83e32 $edx : 0xffffd110 → 0xf7e1eff4 → 0x0021ed8c $esp : 0xffffd0ec → 0xf7c213b5 → add esp, 0x10 $ebp : 0xf7ffd020 → 0xf7ffd9e0 → 0x56555000 → jg 0x56555047 $esi : 0xffffd1a4 → 0xffffd361 → "/home/kali/Desktop/Binary Exploit/victim" $edi : 0xf7ffcb80 → 0x00000000 $eip : 0x5655618d → <main+0> push ebp $eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63 ────────────────────────────────────────────────────────────────────── stack ──── 0xffffd0ec│+0x0000: 0xf7c213b5 → add esp, 0x10 ← $esp 0xffffd0f0│+0x0004: 0x00000002 0xffffd0f4│+0x0008: 0xffffd1a4 → 0xffffd361 → "/home/kali/Desktop/Binary Exploit/victim" 0xffffd0f8│+0x000c: 0xffffd1b0 → 0xffffd38f → "COLORFGBG=15;0" 0xffffd0fc│+0x0010: 0xffffd110 → 0xf7e1eff4 → 0x0021ed8c 0xffffd100│+0x0014: 0xf7e1eff4 → 0x0021ed8c 0xffffd104│+0x0018: 0x5655618d → <main+0> push ebp 0xffffd108│+0x001c: 0x00000002 ──────────────────────────────────────────────────────────────── code:x86:32 ──── 0x56556184 <frame_dummy+4> jmp 0x565560e0 <register_tm_clones> 0x56556189 <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0x5655618c <__x86.get_pc_thunk.dx+3> ret → 0x5655618d <main+0> push ebp 0x5655618e <main+1> mov ebp, esp 0x56556190 <main+3> push ebx 0x56556191 <main+4> sub esp, 0x40 0x56556194 <main+7> call 0x565561c5 <__x86.get_pc_thunk.ax> 0x56556199 <main+12> add eax, 0x2e5b ──────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "victim", stopped 0x5655618d in main (), reason: BREAKPOINT ────────────────────────────────────────────────────────────────────── trace ──── [#0] 0x5655618d → main() ───────────────────────────────────────────────────────────────────────────────── gef➤ |
This is the information before the program runs into the main function, pay attention to the assembly part, we get the function prolog, as follows:
1 2 3 4 5 | 0x5655618d <main+0> push ebp 0x5655618e <main+1> mov ebp, esp 0x56556190 <main+3> push ebx 0x56556191 <main+4> sub esp, 0x40 |
This is why in the illustration above, I have 4 more bytes of the ebx bar. So according to the calculation, to override the eip register, to redirect to the shell code after the main function is done, we need the number of bytes:
1 2 | số bytes = 64 bytes array + 4 bytes ebx + 4 bytes ebp + 4 bytes eip = 76 bytes |
Run the program with input 76 bytes:
1 2 | r `python -c 'print("A"*72+"BBBB")'` |
The results obtained are as follows:
1 2 3 | Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () |
Where 0x42 corresponds to B in ascii. So our calculation is correct. Thanks to eip pointer control, it is now possible to redirect the program as you like. I will do a redirect to a shellcode in the next post.