Writing Shellcode for exit()
Syscall
The steps to write any shellcode are:
- Write and compile C program, for
exit()
example.
exit.c
main()
{
exit(0);
}
We would compile it using:
> gcc -static -o exit exit.c
- Disassemble the compiled program and review the generated assembly. To do this, we would:
> gdb exit
(gdb) disas _exit
Dump of assembler code for function _exit:
0x0806de6a <+0>: mov 0x4(%esp),%ebx
0x0806de6e <+4>: mov $0xfc,%eax
0x0806de73 <+9>: call *%gs:0x10
0x0806de7a <+16>: mov $0x1,%eax
0x0806de7f <+21>: int $0x80
0x0806de81 <+23>: hlt
End of assembler dump.
%gs:0x10
is a reference to a register that protects the stack. See Section 5. In this case, it translates to 0x80
.
There 2 system calls here and 2 instructions above them that provide them their arguments.
This is the first one:
0x0806de6e <+4>: mov $0xfc,%eax
0x0806de73 <+9>: call *%gs:0x10
We can see here that we're loading 0xfc
to EAX
and then calling the syscall. 0xfc
is 252 in decimal and represents exit_group()
which essentially wraps exit()
but also terminates all threads in the process.
This is the second:
0x0806de7a <+16>: mov $0x1,%eax
0x0806de7f <+21>: int $0x80
Here, we're loading 1 into EAX
. syscall 1 is exit()
and calling the system interrupt to execute the syscall.
- Clean up assembly to generify and minimize size of shellcode. The cleaned up assembly would look like
exit_shellcode.asm
:
exit_shellcode.asm
> cat exit_shellcode.asm
Section .text
global _start
_start:
mov ebx,0 ; exit code
mov eax,1 ; syscall exit is 1
int 0x80 ; execute syscall
Use assembler and linker:
#elf == elf32 == elf_i386
nasm -f elf exit_shellcode.asm
ld -m elf_i386 -o exit_shellcode exit_shellcode.o
- Get hexadecimal opcodes from assembly. To do this, we use
objdump
:
> objdump -d exit_shellcode
exit_shellcode: file format elf32-i386
Disassembly of section .text:
08048080 <.text>:
8048080: bb 00 00 00 00 mov $0x0, %ebx
8048085: b8 01 00 00 00 mov $0x1, %eax
804808a: cd 80 int $0x80
We add the opcodes into a character array and use C to execute [e.c
]:
e.c
// e.c
char[] shellcode = "\xbb\x00\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
int main(){
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
Then compile and run it:
> gcc -o execute_exit_shellcode -fno-stack-protector e.c
> ./execute_exit_shellcode
- Test out shellcode. To confirm that the intended shellcode is run, we can use
strace
to check all system calls executed by the binary:
> strace ./execute_exit_shellcode | tail -n 1
exit(0) = ?