Shellcode 是一段设计用于利用计算机系统漏洞的机器代码。它通常是一系列二进制指令,以字节形式表示,并且通常编写为目标特定的机器码,以在目标系统上执行特定的操作。
Overview
Shellcode is widely used in many attacks that involve code injection. Writing shellcode is quite challenging.
Although we can easily find existing shellcode from the Internet, there are situations where we have to write
a shellcode that satisfies certain specific requirements. Moreover, to be able to write our own shellcode from
scratch is always exciting. There are several interesting techniques involved in shellcode. The purpose of
this lab is to help students understand these techniques so they can write their own shellcode.There are several challenges in writing shellcode, one is to ensure that there is no zero in the binary, and
the other is to find out the address of the data used in the command. The first challenge is not very difficult
to solve, and there are several ways to solve it. The solutions to the second challenge led to two typical
approaches to write shellcode. In one approach, data are pushed into the stack during the execution, so their
addresses can be obtained from the stack pointer. In the second approach, data are stored in the code region,
right after a call instruction. When the call instruction is executed, the address of the data is treated as
the return address, and is pushed into the stack. Both solutions are quite elegant, and we hope students can
learn these two techniques. This lab covers the following topics:
• Shellcode
• Assembly code
• Disassembling
本实验给出了一些简单的利用shellcode完成攻击的场景,并介绍了成功编写Shellcode的挑战的注意事项:如何保证二进制中不能出现0x00、如何找到指令中所用到的数据的地址。
实验资料网址:https://seedsecuritylabs.org/Labs_20.04/Software/Shellcode/
实验中各任务的要求参照实验指导PDF。
①使用nasm命令将示例的汇编代码编译成ELF二进制格式。
②使用ld命令将mysh.o编译成可执行二进制文件,-m elf_i386选项意味着生成32位的ELF二进制文件,这个步骤之后,会获得最终的可执行二进制代码文件mysh,运行它,会生成一个shell。
③运行mysh并检测效果。
两次打印的shell的PID不相同,证明mysh确实打开了另外一个shell,功能正常运行。
④在攻击中,起作用的是机器码,而不只是标准的可执行文件。所以我们需要从可执行文件中提取出需要的机器码。
可以使用objdump命令对mysh.o进行反汇编,–Mintel选项是指用Intel的语法模式(另外一种模式是AT&T)。
左边部分是机器码,右边是汇编。
我们再使用xxd命令打印整个二进制程序mysh.o的机器码,在其中找到我们需要的段。
-p 表示使用’plain’输出模式,将文本内容以纯十六机制形式显示,而不包含行号或ASCII文本。
-c 20 指定每行显示20个16进制字符的数量。
这一部分就是我们需要的机器码。
⑤使用实验给出的convert.py程序,将真正的shellcode转换并储存在python的数组中,以便实施攻击。
先将shellcode粘贴到convert.py文件中:
运行convert.py,生成存有shellcode的python数组:
Shellcode 广泛用于缓冲区溢出攻击。但很多情况下,攻击会因为字符串复制而失败,例如strcpy()函数。对于这些字符串复制函数,零被视为字符串的结尾。因此,如果我们在 shellcode 中间有一个零,字符串复制将无法将零之后的任何内容从该 shellcode 复制到目标缓冲区,因此攻击将无法成功。尽管并非所有Shellcode都存在零问题,但 shellcode 要求机器码中不能有任何零;否则,shellcode的应用将会受到限制。
在mysh.s中有4个不同的地方都用到了零,找出mysh.s中所有用到零的地方,并解释代码是如何在不出现零的条件下使用零。
其中1、2、3都是通过xor操作将寄存器的值置为0,而不是直接使用mov指令,如果使用mov指令,那么机器码必然包含0x00。
4的作用是将0x0b赋值给al(eax的低8位),这样eax的值就为0x0000000b,如果用mov eax,0x0b,那么实际的操作数其实是0x0000000b,在内存中有三个0x00。
还有一种避免出现0x00的方法就是“位移”。
如果要将0x007A7978赋值给ebx,如果直接使用mov指令,那么机器码中必然会出现0x00。但是可以先用一个字节的占位符#,即把0x237A7978值赋值给ebx,然后再让ebx左移8位然后又右移8位,这样最终ebx的值会从0x237A7978变成0x007A7978,但是整个指令的机器码中不会含有0x00。
本次任务的要求为:将执行/bin/sh改为执行bin/bash,但是不能通过加多余斜杠的方式来使得补齐push的四字节。
bin/bash一共九个字节,而push是以四个字节为单位进栈的。如果直接push ‘h’会导致最终的机器码中存在0x00。
那么我们可以采用位移的方式解决这个问题。
使用三个占位符#,将’h###’赋值给ecx,然后对让ecx先左移24位然后又右移24位,再push ecx即可。
重新编译可执行文件运行:
查看机器码中是否有0x00。
经验证,机器码中没有0x00。
本次任务是为系统调用提供参数,实现ls -la。
思路:
我们想用execve函数执行/bin/sh -c ls -la,那么我们传入execve的参数应该为:
所以我们先将这些字符串压入栈,通过esp寄存器获得每个字符串的起始位置,最后在依次压入栈调用函数即可。
注意,需要避免机器码中出现0x00,所以当push的值不足4字节的时候需要使用位移的方法。
实验步骤:
在 mysh.s中用汇编代码完成对各个字符串的压栈和函数调用参数地址的压栈。
重新编译运行:
成功输出了当前目录下所有文件的信息,表明实验成功。
在Task 1中,都是通过动态地压入栈,通过esp获得数据的地址。
但还有一种办法,即:将数据储存在代码区,并通过函数的调用机制获得其地址。Task 2就是介绍此方法。
Task 2 中的mysh2.s汇编代码如下:
(1) 从第七行开始逐行解释代码
第7行:将当前栈顶的值弹出,赋给ebp。因为运行call之后,操作系统会自动将call的下一条指令的地址压入栈,而该地址正好是字符串的地址,通过第7行代码将字符串的地址传入ebp,以便进行下一步操作。
第8行:将eax置零。
第9行:将一个字节0x0存储到ebp+7的位置,因为ebp是字符串的地址,那么ebp+7的位置刚好对应字符串中的*占位符,这样做的目的是使用0隔断符截断字符串,使操作系统从ebp读取/bin/sh就结束,不多读取。
第10行:将ebp的值(字符串的地址),放在ebp+8的内存位置。相当Task 1中将argv[0]压入栈。
第11行:将ebx+12地址上的值置为0,相当于Task 1中将argv[1]压入栈,argv[1]的值为0,表示参数数组argv的结束。
第12行:使得ecx = ebx +8,使得ecx的值指向数组argv[],满足exceve函数的传参(ecx传递argv的地址)。
第13行:使得edx为0,满足exceve函数的传参:edx为envp[]的指针,这里为Null表示没有环境变量。
第14、15行:触发系统中断,int 0x80 是一个常用的方式来触发系统调用。0x80 是系统调用的中断号,它告诉操作系统执行特定的系统调用功能。接下来,操作系统会根据 eax 寄存器的值来确定要执行哪个系统调用,而其他寄存器则用于传递参数和返回结果。
(2) 实现/usr/bin/env并打印a=11 b=22
思路:
要实现该功能的命令为:/usr/bin/env – a=11 b=22
所以我们的目的就是将execve函数中的argv[]参数,设置为/usr/bin/env – a=11 b=22。那么argv数组将会有五个元素(最后一个为Null)。
所以我们这样设计:
AAAA存放usr/bin/env
的地址,BBBB存放-
的地址,CCCC存放a=11
的地址,DDDD存放b=22
的地址,EEEE置0,表示Null,表示argv数组结尾。
然后还要把ecx设为&argv[0],满足对execve函数传参的时候;将edx设置为0,表示没有环境变量。
实验操作:
修改mysh2.s代码,通过汇编代码实现思路。
编译成二进制文件运行:
查看机器码,有无0x00:
经检验,能正常满足任务要求,并且机器码中无0x00。
Task 2完成。