Skip to main content

Writing Shellcode for exit() Syscall

The steps to write any shellcode are:

  1. Write and compile C program, for exit() example.
exit.c
main()
{
exit(0);
}

We would compile it using:

> gcc -static -o exit exit.c
  1. 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.

  1. 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
  1. 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
  1. 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) = ?