
Suppose we want to recreate the "Hello World!" shellcode but in C using functions and declaring global strings and stuff like that. Normal relaxed plain C like this..
- int write(int fd, void* buffer, unsigned int size){
- int ret;
- asm volatile (
- "movl $4, %%eax\n\t"
- "movl %1, %%ebx\n\t"
- "movl %2, %%ecx\n\t"
- "movl %3, %%edx\n\t"
- "int $0x80"
- : "=a"(ret)
- : "g"(fd), "g"(buffer), "g"(size)
- : "%ebx"
- );
- return ret;
- }
- char message[] = "HOLA MUNDO!(shellcode)\n";
- int shellcode(){
- write(1,message,sizeof(message));
- }
Well not that relaxed. In a shellcode environment we don't have libc nor any other function ready to use. We have 2 ways to mitigate this:
- Perform system calls directly int 80/systenter (works only for POSIX)
- Solve and use already loaded api libraries (preferred in win32)
In our linux example we use software interruption 0x80 to make system calls directly. System call number 4 is the write system call in any linux. In order to do the same in Windows we need to do some other magic to learn the address of some system DLLs and then search for the desired function.
Compiling the C shellcode
Let's use gcc to compile our C code into object code.
$ gcc -m32 -march=i386 -fno-stack-protector -fno-common -Os -fomit-frame-pointer -fno-PIC -c -static shellcode.c
We have added some command line arguments to simplify a bit the generated object code. We can inspect shellcode.o with objdump, it has two interesting sections: .text and .data.
$ objdump -s shellcode.o
shellcode.o: file format elf32-i386
Contents of section .text:
0000 53b80400 00008b5c 24088b4c 240c8b54 S......\$..L$..T
0010 2410cd80 5bc353b8 04000000 bb010000 $...[.S.........
0020 00b90000 0000ba18 000000cd 805bc3 .............[.
Contents of section .data:
0000 484f4c41 204d554e 444f2128 7368656c HOLA MUNDO!(shel
0010 6c636f64 65290a00 lcode)..
and one relocation; which is the message section .data referenced from the code in section .text.
$ objdump -r shellcode.o
shellcode.o: file format elf32-i386
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000022 R_386_32 message
The loader
To fix the relocations we need to know the starting address of every section in memory. We can't force or estimate in advance where the shellcode is going to be placed so the addresses of each section must be solved at runtime. That's easy to do in x86.- .section .text
- #esp shall point to writeable memory
- jmp labelA
- dummy:
- jmp begin
- labelA:
- call dummy
- begin:
- #read eip into esi
- popl %esi
We take the address of our code from the stack issuing a dummy function call. We still have to do some offsets calculations to reach the start of each section. Lets layout the sections in this way:
[LOADER][METADATA][.text][.data]
The metadata contains the offsets of every relocation in the following sections. As loader knows where is itself in memory, it can find and process the metadata and fix the relocations. Of course the metadata must be previously generated using the information in the object file. We'll see how to build it with a small python script in a while, meanwhile let's agree in its format:
[N|RELOC1|RELOC2|....|RELOCN][START]
- subl $(begin-relocs), %esi #esi points to [METADATA]
- movl (%esi), %ecx #Number of relocations N
- leal 8(%esi,%ecx,4), %edi #Calculate the start of .text
- andl %ecx,%ecx
- jz done
- fix_reloc:
- movl (%esi,%ecx,4), %eax #Get a relocation offset
- addl %edi, (%edi,%eax,1) #Add the start of .text
- dec %ecx
- jne fix_reloc
- done:
- addl -4(%edi), %edi #START plus the start of .text
- jmp *%edi #gets the entry point
- relocs:
Now we need to prepare an ad-hoc metadata section from the object file and concatenate everything in an opaque chunk of data.
The python script: mkShellcode.py
To handle the object file (which is in fact an ELF file) we use pyelftools as in the previous shellcode post. This script must do 3 simple tasks:- Extract and concatenate every important section.
- Parse the relocations and symbols from the ELF and build the metadata chunk
- Prepend the loader binary
You may check out the complete script from here.
Try it...
Step 1 - Code your C shellcode
Mostly your thing, just remember not to use anything outside your project and to define the shellcode() funtion. Wild idea, in Linux you may consider using uClibc. An example file here.
Step 2 - Compile your code
You should use a command line like the following:
$ gcc -m32 -march=i386 -fno-stack-protector -fno-common -Os -fomit-frame-pointer -fno-PIC -c -static shellcode.c
Check out gcc manpage for details. Mandatory: -c -fno-common -fno-PIC -static
Step 3 - Run it through our mkShellcode script
$ python mkShellcode.py shellcode.o shellcode.bin
Step 4 - Test it
Use the test.c. It will load the file passed as argument in an executable memory and pass the control to it.
$ gcc -m32 test.c -o test32 $ ./test32 shellcode.bin Memory@0xf76e0000 Passing control to the shellcode... HOLA MUNDO!(shellcode) The shellcode has returned to main!
No comments:
Post a Comment