作业题目
shellcode广泛用于许多涉及代码注入的攻击中。编写shellcode是相当有挑战性的。虽然我们可以很容易地从互联网上找到现有的shellcode,但是能够从头开始编写我们自己的shellcode总是令人兴奋的。shellcode中涉及到几种有趣的技术。本实验室的目的是帮助学生理解这些技术,以便他们能够编写自己的shellcode。
编写shellcode有几个挑战,一个是确保二进制文件中没有0x00,另一个是找出命令中使用的数据的地址。第一个挑战不是很难解决,有几种方法可以解决它。第二个挑战的解决方案导致了编写外壳代码的两种典型方法。在一种方法中,数据在执行期间被推入堆栈,因此可以从堆栈指针获得它们的地址。在第二种方法中,数据存储在代码区域中,就在调用指令之后,因此在调用调用函数时,其地址被推入堆栈(作为返回地址)。两种解决方案都非常优雅,我们希望学生能够学习这两种技术。
实验步骤及结果
Task 1.a: The Entire Process
- 分析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’’ 的结束。
B:将字符串动态地压入栈,新添加的元素总是在栈顶,通过读取esp就能知道字符串的地址。把 esp 的内容放入 ebx,就是把命令字符串的地址保存到 ebx 寄存器中。
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。
- 完成整个流程
将.s文件编译为.o文件,再将.o文件编译为可执行文件mysh。打印当前shell的进程ID为3132,执行mysh后,进程ID为3391与之前不一样,说明mysh启动了一个新的shell。
得到mysh.o的机器码(没有0)。
使用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作为字符串结束符。
运行后,成功开启一个新shell。
机器码中没有0。
Task 1.c. Providing Arguments for System Calls
在mysh.s基础上进行修改:
将字符串压入栈,再将它们的存储地址存入寄存器中。(利用1.b中的方法保证代码中没有0并以4byte为一组)。
压入字符串存储地址,构造argv[]数组,再将argv[]数组地址存入ecx。
运行后得到结果如上。
与直接执行命令“/bin/sh -c “ls -al””得到的结果对比:
一致。
查看机器码:
机器码中没有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里。
将edx置为0,压入栈,再将eax压入栈(eax指向“cccc=123”所存储的地址)。在eax上加0xc,这时eax指向“bbb=5678”,压入eax,将“bbb=5678”的存储地址压入了栈中,压入“aaa=1234”的存储地址同理。
运行代码:
成功打印环境变量。
查看机器码:
机器码中没有0。
Task 2: Using Code Segment
逐行解释代码:
(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)
创建一个字符串‘/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选项让代码段可写。
执行:
成功。
Task 3: Writing 64-bit Shellcode
对于x64体系结构,调用系统调用是通过系统调用指令完成的,系统调用的前三个参数分别存储在rdx、rsi、rdi寄存器中。
代码如下:
在64-bit shellcode中,以8byte为一组分割命令字符串,将字符串存入rax,再将rax压入栈。
执行代码:
成功。
查看机器码:
机器码中没有0。
如果直接使用 push “/bin/bas” 压入栈,会遇到关于“立即数超出边界”的警告,最终导致segmentation fault。
标签:bin,压入,eax,地址,实验,字符串,编写,shellcode From: https://www.cnblogs.com/wxrwajiez/p/18516097