网络攻防-软件漏洞2-shellcode编写,涉及实验:SEED Labs – Shellcode Development Lab ,代码见:https://github.com/Seanxz401/seed-labs
理论
ShellCode就是一段能够完成一定功能、可直接由计算机执行的机器代码,通常以十六进制 的形式存在。
常见功能:发起反向连接、上传/下载木马文件、删除/窃取重要数据……
Windows函数调用原理:
- 加载函数所在的动态链接库
- 使用堆栈进行参数传递
- 调用函数地址
实践-用汇编写Shellcode(编译后的机器码就是),跳过跳过,汇编指令看得头疼,需要注意的问题:
- 扫尾工作:使程序正常退出
- 处理null字节:可替代指令&指令编码、编码器
- 对函数地址采用硬编码会导致不通用。
通用Shellcode的编写办法
- 将每个版本的Windows操作系统所地应的函数的地址列出来,然后针对 不同版本的操作系统使用不用的地址;
- 动态定位函数地址:使用GetProcAddress和LoadLibrary函数动态获取 其它函数的地址。
获取GetProcess和LoadLibrary地址的方法:
- 暴力搜索:寻找MZ和PE标志
- 使用PEB获取GetProcAddress地址:PEB进程环境块
- SEH获得kernel基址:SEH强制产生一个异常调用
- HASH法查找所有函数地址
实验
Task1
1.a mysh
首先对已有的代码文件mysh.s进行编译链接:
1 | # Compiling to object code. |
获取到可执行二进制文件mysh后,mysh
执行,可以得到一个shell,执行echo $$
打印当前shell的进程ID
利用objdump
从mysh.o中获取汇编代码:
1 | objdump -Mintel --disassemble mysh.o |
利用xxd -p -c 20 mysh.o
也可以得到机器码,但是是用字节表示的,因此我们可以利用convert.py将xxd
的输出变成数组形式以便于插入攻击代码中。
1.b 从代码中消除零
在缓冲区溢出攻击中我们利用了strcpy函数不检查数组边界的特性,但是它会检查字符串结尾符(0x00),在复制过程中遇到0就会终止。在mysh.s中有4处用到0但不在机器码中直接体现的情况:
- 参数0-eax寄存器,
xor eax, eax
对相同值逐位异或得到全零的eax寄存器。 - 参数1-ebx寄存器,保存命令字符串
bin/bash
的地址,借用eax构造字符串终止符0x00
。 - eax寄存器,保存11(execve的系统调用号)。直接赋值
mov eax 0x0000000b
(32位)会出现截止符,通过以下两个步骤xor eax, eax
:eax清零;mov al,0x0b
:将8位数据0x0b
传递到AL(eax的低8位)
- edx寄存器,保存想传给新程序的环境变量地址,通过
xor edx, edx
使得edx=0.
task:不通过增加斜杠/
的方法获取shell(多个斜杠在系统处理中被理解为1个,push必须接32位的数)。我们选择#
作为填充字符,然后通过移位的方式得到0。代码修改:
1 | xor eax, eax ;eax异或清零 |
编译执行,获得shell。
1.c 添加参数
思路与1.b类似,对参数进行拆分,需要0x00截止符的时候就通过移位或者补充斜杠来获得。获取参数地址:
1 | ; Store the argument string on stack |
然后将参数逆序压栈,编译执行结果如下:成功执行ls -la
,图略
1.d 传环境变量
字符串的构造与上文类似,移位构造截止符。环境变量的数组构造部分:(高地址先入栈)
1 | ; For environment variable |
然后把execve的第一个参数改为/usr/bin/env
,编译执行,输出了三个变量
Task2
代码解释
1 | one: |
- 程序先
jmp short two
开始执行two函数,进入two后又调用了one - call调用的下一条语句是存储了一个字符串,调用函数时会将下一条语句的地址压入栈作为返回地址,因此字符串的地址被压入栈;
- 到了one函数中,调用了pop把栈顶(字符串的地址)取出并放入了ebx中,这样就获取到了字符串的地址;
- 后面内容与mysh.s类似,将字符串中的*占位符和参数1用0覆盖,将ecx指向参数地址,为eax赋值11(execve的调用值)。
修改代码
修改代码输出环境变量:
1 | one: |
重难点:对lea和mov的理解;数组在内存中的存储情况。
Task3
仿照Task1.b实现64位的shellcode编写,即利用移位获取0x00.主要修改如下:
1 | mov rax, "h#######" |
总结
Task用到的一些方法:
- 异或清零,并通过
al
,ax
,eax
,rax
获取8位,16位,32位,64位的零; - 移位:在字符串末尾填充一定数量的字符,并通过逻辑移位获得末尾的0;
- 先通过占位符传入一定长度的字符串,获取到字符串首地址后可以根据偏移量对其他位置进行操作。
参考
http://note.blueegg.net.cn/seed-labs/overflow/shellcode/