shellcode编写实验

 

 

 

作业题目

shellcode广泛用于许多涉及代码注入的攻击中。编写shellcode是相当有挑战性的。虽然我们可以很容易地从互联网上找到现有的shellcode,但是能够从头开始编写我们自己的shellcode总是令人兴奋的。shellcode中涉及到几种有趣的技术。本实验室的目的是帮助学生理解这些技术,以便他们能够编写自己的shellcode。

编写shellcode有几个挑战,一个是确保二进制文件中没有0x00,另一个是找出命令中使用的数据的地址。第一个挑战不是很难解决,有几种方法可以解决它。第二个挑战的解决方案导致了编写外壳代码的两种典型方法。在一种方法中,数据在执行期间被推入堆栈,因此可以从堆栈指针获得它们的地址。在第二种方法中,数据存储在代码区域中,就在调用指令之后,因此在调用调用函数时,其地址被推入堆栈(作为返回地址)。两种解决方案都非常优雅,我们希望学生能够学习这两种技术。

实验步骤及结果

Task 1.a: The Entire Process

  1. 分析mysh.s

execve()是用来执行新程序的系统调用。这个调用会替换当前进程的映像(包括代码和数据),用新的程序文件(通常是一个二进制文件)来加载到内存并开始执行。

shellcode 最核心的部分是使用 execve() 系统调用来执行“/bin/sh”。

使用这个系统调用,需要设置以下 4 个寄存器:

(1) eax 寄存器: 必须保存 11, 11 是 execve() 的系统调用号。

(2) ebx 寄存器: 必须保存命令字符串的地址 (如“/bin/sh”)。

(3) ecx 寄存器: 必须保存参数数组的地址。

(4) edx 寄存器: 必须保存想要传给新程序的环境变量的地址。

还需要注意,一些函数把 0 视作数据复制源的结尾,为了确保完整的代码被复制进目标缓冲区, 尽管程序中需要使用 0 值, 但不能让 0 在代码中出现。

xor eax, eax 可以将eax置为0而代码中不出现0。

捕获

A:栈一次只能压入4个byte,所以将字符串以4个byte分为一份。不够的补上一个“/”变成“//sh”(execve()还是会按照一个“/”处理)。最后把 0(eax) 压入栈中, 代表字符串 “/bin//sh’’ 的结束。

捕获捕获2

B:将字符串动态地压入栈,新添加的元素总是在栈顶,通过读取esp就能知道字符串的地址。把 esp 的内容放入 ebx,就是把命令字符串的地址保存到 ebx 寄存器中。

捕获捕获3

C:构造argv[]数组,数组的第一个元素指向字符串 “/bin//sh”, 第二个元素是 0, 标志着数组的末尾。ecx寄存器保存argv[]数组的地址。

D:edx设成 0, 不传递任何环境变量。

E:0xb=11,将eax保存为execve() 的系统调用号11。

F:寄存器(eax,ebx,ecx,edx)的值被正确设置, 执行“int $0x80”指令后, 系统调用 execve() 将开启一个 shell。

  1. 完成整个流程

task1

将.s文件编译为.o文件,再将.o文件编译为可执行文件mysh。打印当前shell的进程ID为3132,执行mysh后,进程ID为3391与之前不一样,说明mysh启动了一个新的shell。

task1.2task1.3

得到mysh.o的机器码(没有0)。

task1.4

使用convert.py将机器码转换为shellcode。

Task 1.b. Eliminating Zeros from the Code

“/bin/bash”一共9个byte,以4个byte为一组还差3个byte,但题目规定不能用“/”。

替换

将ebx向左移24位,“###”被丢弃,再向右移24位,变为“h000”,凑够了位数,又在不引入0的情况下使用0作为字符串结束符。

task1.6task1.7

运行后,成功开启一个新shell。

task1.8task1.9

机器码中没有0。

Task 1.c. Providing Arguments for System Calls

在mysh.s基础上进行修改:

捕获

将字符串压入栈,再将它们的存储地址存入寄存器中。(利用1.b中的方法保证代码中没有0并以4byte为一组)。

捕获

压入字符串存储地址,构造argv[]数组,再将argv[]数组地址存入ecx。

task2.2

运行后得到结果如上。

与直接执行命令“/bin/sh -c “ls -al””得到的结果对比:

task2.1

一致。

查看机器码:

task2.3task2.4

机器码中没有0。

Task 1.d. Providing Environment Variables for execve()

在mysh.s的基础上修改:

捕获

A:将命令字符串“/usr/bin/env”压入栈

B:将环境变量字符串压入栈。

“cccc=1234”不是4的倍数,以4byte分组后将多出来的“4”放入al中(al是eax的低八位),再将eax压入栈,使字符串完整入栈,且结束符也入栈。

存储字符串的地址存在eax里。

捕获2IMG_0194(20231019-105850)

将edx置为0,压入栈,再将eax压入栈(eax指向“cccc=123”所存储的地址)。在eax上加0xc,这时eax指向“bbb=5678”,压入eax,将“bbb=5678”的存储地址压入了栈中,压入“aaa=1234”的存储地址同理。

运行代码:

task3

成功打印环境变量。

查看机器码:

task3.2task3.3

机器码中没有0。

Task 2: Using Code Segment

逐行解释代码:

task4

捕获

(1)why this code would successfully execute the /bin/sh program, how the argv[] array is constructed?

代码创建了命令字符串“/bin/sh”和占位符构成的字符串,通过函数调用会存储返回地址(原函数下一条指令)的原理,使用pop得到字符串的地址,通过这个地址,我们修改代码段,为命令字符串加上结束符,将原本的占位符“AAAA”替换为命令的地址,“BBBB”替换为0,构造argv[]数组。最后,做好系统调用execve()前的准备(为eax,ebx,ecx,edx设置正确的值),调用execve(),执行“/bin/sh”开启新的shell。

(2)

task6.2

创建一个字符串‘/usr/bin/env****argv****aa=11*bb=22*env1env2****’,再将字符串替换为‘/usr/bin/env%0%0%0%0

(/usr/bin/env的地址)

%0%0%0%0

aa=11%0bb=22%0

(aa=11的地址)(bb=22的地址)%0%0%0%0’

再将eax,ebx,ecx,edx设置为应该的值,系统调用execve()。

默认情况下,代码段不可写的,所以,在运行链接器程序(ld)时,我们需要使用——omagic选项让代码段可写。

执行:

task6

成功。

Task 3: Writing 64-bit Shellcode

对于x64体系结构,调用系统调用是通过系统调用指令完成的,系统调用的前三个参数分别存储在rdx、rsi、rdi寄存器中。

IMG_0195(20231019-194248)

代码如下:

task7.3

在64-bit shellcode中,以8byte为一组分割命令字符串,将字符串存入rax,再将rax压入栈。

执行代码:

task7

成功。

查看机器码:

task7.2

机器码中没有0。

如果直接使用 push “/bin/bas” 压入栈,会遇到关于“立即数超出边界”的警告,最终导致segmentation fault。

捕获

posted @ 2024-10-30 16:49  wajiez  阅读(258)  评论(0)    收藏  举报