编写并提取通用 ShellCode
简易 ShellCode 虽然可以正常被执行,但是还存在很多的问题,因为上次所编写的 ShellCode 采用了硬编址的方式来调用相应API函数的,那么就会存在一个很大的缺陷,如果操作系统的版本不统一就会存在调用函数失败甚至是软件卡死的现象,下面我们通过编写一些定位程序,让 ShellCode 能够动态定位我们所需要的API函数地址,从而解决上节课中 ShellCode 的通用性问题。
### 查找 Kernel32.dll 基址
首先我们需要通过汇编的方式来实现动态定位 Kernel32 中的基地址,你或许会有个疑问? 为什么要查找 Kernel32.dll 的地址而不是 User32.dll,这是因为我们最终的目的是调用 MessageBox 这个函数,而该函数位于 User32.dll 这个动态链接库里,默认情况下是无法直接调用的,为了能够调用这个函数,我们就需要调用 LoadLibraryA 函数来加载 User32.dll 这个模块,而 LoadLibraryA 又位于 kernel32.dll 中。
恰巧的是 Kernel32.dll 这个模块只要是 PE 文件都会默认被加载 ,因此我们只需要找到 LoadLibraryA 函数,即可加载任意的动态链接库,并调用任意的函数啦。
由于我们需要动态获取 LoadLibraryA() 以及 ExitProcess() 这两个函数的地址,而这两个函数又是存在于 kernel32.dll 中的,因此这里需要先找到 kernel32.dll 的地址,然后通过对其进行解析,从而查找那两个函数的地址。
这里有一个公式,可以动态的查找到 Kernel32.dll 的地址,如下:
- 通过段选择字FS在内存中找到当前的线程环境块TEB。
- 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针。
- 进程环境块偏移为 0x0c 存放着指向 PEB_LDR_DATA 的结构体指针。
- PEB_LDR_DATA 偏移 0x1c 的地方存放着指向模块初始化链表的头指针。
- 初始化链表中,按顺序存放着PE装入运行时初始化模块的相关信息。
既然有了固定的公式,接下我们就使用WinDBG调试器来手工完成对 Kernel32.dll 地址的定位:
1.首先我们运行 WinDBG调试器,然后按下【Ctrl + K】选择文件(File) -> 选择内核调试(Kernel Debug) -> 本地调试(Local) 点击确定。打开后会看到如下界面,直接在最底部输入两条命令,来加载一下符号文件,否则无法进行查看。

2.通过段选择字FS在内存中找到当前的线程环境块TEB。这里可以利用本地调试,输入!teb 指令:

线程环境块偏移位置为 0x30 的地方存放着指向进程环境块PEB的指针。结合上图可见,PEB的地址为0x7ffd7000。
3.进程环境块中偏移位置为 0x0c 的地方存放着指向 PEB_LDR_DATA 结构体的指针,其中存放着已经被进程装载的动态链接库的信息。

4.接着 PEB_LDR_DATA 结构体偏移位置为 0x1c 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。

5.模块初始化链表 InInitializationOrderModuleList 中按顺序存放着PE装入运行时初始化模块的信息,第一个链表节点是 ntdll.dll,第二个链表结点就是kernel32.dll。比如可以先看看
InInitializationOrderModuleList 中的内容:

上图中的 0x00191f28 保存的是第一个链节点的指针,解析一下这个结点,可发现如下地址:

上图中 0x7c92000为 ntdll.dll 的基地址,而 0x00191fd0 则是下一个模块的指针,继续跟随 0x00191fd0 。

观察发现第二个节点偏移 0x08 个字节正是 kernel32.dll的基地址,其地址为 0x7c800000。最有我们来验证一下:

6.通过上方的调试我们可得到公式,接着通过编写一段汇编代码来实现自动的遍历出 kernel32.dl 的基址。
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kerbcli.lib
assume fs:nothing
.code
main PROC
xor eax,eax
xor edx,edx
mov eax,fs:[30h] ; 得到PEB结构地址
mov eax,[eax + 0ch] ; 得到PEB_LDR_DATA结构地址
mov esi,[eax + 1ch]
lodsd ; 得到KERNEL32.DLL所在LDR_MODULE结构的
; mov eax,[eax] ; Windows 7 以上要将这里打开
mov edx,[eax + 8h] ; 得到BaseAddress,既Kernel32.dll基址
ret
main ENDP
END main
### 计算函数名 hash 摘要
接着需要对我们所用到的字符串进行 hash 压缩处理,为啥要压缩? 原因是如果直接将函数名压栈的话,我们就需要提供更多的空间来存储 ShellCode 代码,为了能够让我们编写的 ShellCode 代码更加的短小精悍,所以我们将要对字符串进行hash处理,将字符串压缩为一个十六进制数,这样只需要比较二者hash值就能够判断目标函数,尽管这样会引入额外的hash算法,但是却可以节省出存储函数名字的空间。
如下代码是使用 Win32 汇编语言的实现过程,并在 MASM 上正常编译,汇编版字符串转换Hash值。
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
include msvcrt.inc
includelib kernel32.lib
includelib msvcrt.lib
.data
data db "MessageBoxA",0h
Fomat db "0x%x",0
.code
main PROC
xor eax,eax ; 清空eax寄存器
xor edx,edx ; 清空edx寄存器
lea esi,data ; 取出字符串地址
loops:
movsx eax,byte ptr[esi] ; 每次取出一个字符放入eax中
cmp al,ah ; 验证eax是否为0x0即结束符
jz nops ; 为0则说明计算完毕跳转到nops
ror edx,7 ; 不为零,则进行循环右移7位
add edx,eax ; 将循环右移的值不断累加
inc esi ; esi自增,用于读取下一个字符
jmp loops ; 循环执行
nops:
mov eax,edx ; 结果存在eax里面
invoke crt_printf,addr Fomat,eax
ret
main ENDP
END main
当然也可以使用C语言来实现这个转换的过程,这里使用的是VS Express 2013可正常编译通过,如下代码:
#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
DWORD digest = 0;
while (*fun_name)
{
digest = ((digest << 25) | (digest >> 7));
digest += *fun_name;
fun_name++;
}
return digest;
}
int main()
{
DWORD hash;
hash = GetHash("MessageBoxA");
printf("0x%.8x\n", hash);
getchar();
return 0;
}
我们通过传入不同的函数名称,让其计算出我们所需要计算的三个函数的hash值,其计算结果如下所示:
MessageBoxA 0x1e380a6a
ExitProcess 0x4fd18963
LoadLibraryA 0x0c917432
### 解析 kernel32.dll 导出表
在开头的部分我们通过 WinDBG 调试器已经找到了 Kernel32.dll 这个动态链接库的基地址,而Dll文件本质上也是PE文件,在Dll文件中存在一个导出表,其内部记录着该Dll的导出函数。接着我们需要对Dll文件的导出表进行遍历,不断地搜索,从而找到我们所需要的API函数。
同样的,这里有一个定式,可以通过该定式获取到指定的导出表。
- 从 kernel32.dll 加载基址算起,偏移 0x3c 的地方就是其PE文件头。
- PE文件头偏移 0x78 的地方存放着指向函数导出表的指针。
- 导出表偏移0x1c处的指针指向存储导出函数偏移地址(RVA)的列表。
- 导出表偏移0x20处的指针指向存储导出函数函数名的列表。
函数的 RVA 地址和名字按照顺序存放在上述两个列表中,我们可以在列表定位任意函数的RVA地址,通过与动态链接库的基地址相加得到其真实的VA,而计算的地址就是我们最终在 ShellCode 中调用时需要的地址。
pushad ; 保护所有寄存器中的内容
mov eax,[ebp+0x3C] ; 指向PE文件头
mov ecx,[ebp+eax+0x78] ; 导出表的指针
add ecx,ebp
mov ebx,[ecx+0x20] ; 导出函数的名字列表
add ebx,ebp
xor edi,edi ; 用edi寄存器作为索引
; ------- 循环读取导出表函数
next_loop:
inc edi ; 不断自增索引
mov esi,[ebx+edi*4] ; 从列表数组中读取
add esi,ebp ; esi保存的是函数名称所在地址
cdq
### 提取代码 ShellCode
完整代码如下,下方代码是一个定式,这里就只做了翻译,使用编译器编译如下代码,运行后会弹出一个提示框hello lyshark说明我们成功了。
#include <stdio.h>
#include <windows.h>
int main()
{
__asm {
// ===将索要调用的函数hash值入栈保存
CLD // 清空标志位DF
push 0x1E380A6A // 压入MessageBoxA-->user32.dll
push 0x4FD18963 // 压入ExitProcess-->kernel32.dll
push 0x0C917432 // 压入LoadLibraryA-->kernel32.dll
mov esi,esp // 指向堆栈中存放LoadLibraryA的地址
lea edi,[esi-0xc] // 后面会利用edi的值来调用不同的函数
// ===开辟内存空间,这里是堆栈空间
xor ebx,ebx
mov bh,0x04 // ebx为0x400
sub esp,ebx // 开辟0x400大小的空间
// ===将user32.dll入栈
mov bx,0x3233
push ebx // 压入字符'32'
push 0x72657375 // 压入字符 'user'
push esp
xor edx,edx // edx=0
// ===查找kernel32.dll的基地址
mov ebx,fs:[edx+0x30] // [TEB+0x30] -> PEB
mov ecx,[ebx+0xC] // [PEB+0xC] -> PEB_LDR_DATA
mov ecx,[ecx+0x1C] // [PEB_LDR_DATA+0x1C] -> InInitializationOrderModuleList
mov ecx,[ecx] // 进入链表第一个就是ntdll.dll
mov ebp,[ecx+0x8] //ebp = kernel32.dll 的基地址
// === hash 的查找相关
find_lib_functions:
lodsd // eax=[ds*10H+esi],读出来是LoadLibraryA的Hash
cmp eax,0x1E380A6A // 与MessageBoxA的Hash进行比较
jne find_functions // 如果不相等则继续查找
xchg eax,ebp
call [edi-0x8]
xchg eax,ebp
// ===在PE文件中查找相应的API函数
find_functions:
pushad
mov eax,[ebp+0x3C] // 指向PE头
mov ecx,[ebp+eax+0x78] // 导出表的指针
add ecx,ebp // ecx=0x78C00000+0x262c
mov ebx,[ecx+0x20] // 导出函数的名字列表
add ebx,ebp // ebx=0x78C00000+0x353C
xor edi,edi // 清空edi中的内容,用作索引
// ===循环读取导出表函数
next_function_loop:
inc edi // edi作为索引,自动递增
mov esi,[ebx+edi*4] // 从列表数组中读取
add esi,ebp // esi保存的是函数名称所在的地址
cdq
// ===hash值的运算过程
hash_loop:
movsx eax,byte ptr[esi] // 每次读取一个字节放入eax
cmp al,ah // eax和0做比较,即结束符
jz compare_hash // hash计算完毕跳转
ror edx,7
add edx,eax
inc esi
jmp hash_loop
// ===hash值的比较函数
compare_hash:
cmp edx,[esp+0x1C]
jnz next_function_loop // 比较不成功则查找下一个函数
mov ebx,[ecx+0x24] // ebx=序数表的相对偏移量
add ebx,ebp // ebx=序数表的绝对地址
mov di,[ebx+2*edi] // di=匹配函数的序数
mov ebx,[ecx+0x1C] // ebx=地址表的相对偏移量
add ebx,ebp // ebx=地址表的绝对地址
add ebp,[ebx+4*edi] // 添加到EBP(模块地址库)
xchg eax,ebp // 将func addr移到eax中
pop edi // edi是pushad中最后一个堆栈
stosd
push edi
popad
cmp eax,0x1e380a6a // 与MessageBox的hash值比较
jne find_lib_functions
// ===下方的代码,就是我们的弹窗
function_call:
xor ebx,ebx // 清空eb寄存器
push ebx // 截断字符串0
push 0x2020206b
push 0x72616873
push 0x796c206f
push 0x6c6c6568 // push hello lyshark
mov eax,esp
push ebx // push 0
push eax // push "hello lyshark"
push eax // push "hello lyshark"
push ebx // push 0
call [edi-0x04] // call MessageBoxA
push ebx // push 0
call [edi-0x08] // call ExitProcess
}
return 0;
}
编译生成可执行文件以后,我们使用OD打开程序,并手工寻找到程序的 OEP 提取出 ShellCode 的机器码,打开UltraEdit 工具,粘贴机器码,然后按下【Alt + C】进入列模式,编辑只保留机器码即可。
编写并提取通用 ShellCode的更多相关文章
- 编写并提取简易 ShellCode
ShellCode 通常是指一个原始的可执行代码的有效载荷,ShellCode 这个名字来源于攻击者通常会使用这段代码来获得被攻陷系统上的交互 Shell 的访问权限,而现在通常用于描述一段自包含的独 ...
- 通用shellcode
所有 win_32 程序都会加载 ntdll.dll 和 kernel32.dll 这两个最基础的动态链接库.如果想要 在 win_32 平台下定位 kernel32.dll 中的 API 地址,可以 ...
- 设计和编写一个异步通用Picker选择器,用于时间日期、城市、商品分类的选择
目录 一.功能规划 二.最底层基础实现 (1)Picker界面和功能实现 (2)不同类型的选择器基础实现 三.数据源层 (1)时间日期 (2)多级同步分类,如:城市 (3)多级异步分类,如:城市 四. ...
- 如何编写测试团队通用的Jmeter脚本
平时学习.工作过程中,编写的一些jmeter脚本,相信大多数都遇到过这个问题.那就是:如果换一台电脑运行,文件路径不一样,会导致运行失败. 前不久,自己就真真切切遇到过一回,A同学写了个脚本用于压测, ...
- 66-Flutter移动电商实战-会员中心_编写ListTile的通用方法
1.界面分析 通过下图我们可以拆分成 4 部分,头部.订单标题区域.订单列表区域.ListTitle同用部分. 2.UI编写 2.1.头部 主要用到了圆形头像裁剪组件-ClipOval 顶部头像区域W ...
- matlab逐行读取text文件,编写函数提取需要的文字
在数学建模中遇到的数据比较难处理,而且给的是text格式,自己想了好长时间才编出来,现在分享一下,可以交流学习 目标的text文件是 只提取里面的数据 需要自编函数 clc,clear path='D ...
- C#编写的序列化通用类代码
using System; using System.IO; using System.IO.Compression; using System.Runtime.Serialization.Forma ...
- 通用shellcode代码
#include <stdio.h>#include <windows.h> int main(){ __asm { CLD //清空标志位DF push 0x1E380A6A ...
- Linux下shellcode的编写
Linux下shellcode的编写 来源 https://xz.aliyun.com/t/2052 EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技 ...
随机推荐
- 微信小程序wx.request的简单封装
前言 之前写小程序,每次请求后台时都直接调用原生的API,wx.request,每次都要写url,data,回调函数等,正好前段时间,小程序项目需要添加新内容,趁此机会,做一个封装的请求工具,比较简单 ...
- 一个读取C#特性Description方法(zhuan)
class Program { static void Main(string[] args) { string str= DB.write.ToDescription(); Console.Writ ...
- SQL-W3School-函数:SQL FORMAT() 函数
ylbtech-SQL-W3School-函数:SQL FORMAT() 函数 1.返回顶部 1. FORMAT() 函数 FORMAT 函数用于对字段的显示进行格式化. SQL FORMAT() 语 ...
- 012-多线程-JUC集合-Queue-SynchronousQueue和LinkedTransferQueue
一.SynchronousQueue概述 SynchronousQueue是一个不存储元素的队列.每一个put操作必须等待一个take操作,否则不能继续添加元素. 它支持公平访问队列.默认情况下线程采 ...
- ORA-00911: invalid character 错误解决集锦
转: ORA-00911: invalid character 错误解决集锦 参考https://www.linuxidc.com/Linux/2017-05/144361.htm ORA-00911 ...
- 不同三维引擎渲染IFC数据效果对比
- Greenwich.SR2版本的Spring Cloud Hystrix实例
之前我们在eureka(参见Greenwich.SR2版本的Spring Cloud Eureka实例)中,服务消费方a-beautiful-client调用服务提供方a-bootiful-clien ...
- 阶段5 3.微服务项目【学成在线】_day17 用户认证 Zuul_14-网关-介绍网关及搭建网关工程
4 Zuul网关 4.1 需求分析 网关的作用相当于一个过虑器.拦截器,它可以拦截多个系统的请求. 本章节要使用网关校验用户的身份是否合法. 4.2 Zuul介绍 什么是Zuul? Spring Cl ...
- Arduino---HC-05 蓝牙模块
蓝牙基础知识回顾: (一)Arduino和HC-05连接 注意:Arduino通过TX与HC-05进行通信,而Arduino的电压为5V,HC-05的允许电压为3.3V.短时间通信无妨(长时间可能烧毁 ...
- PPT做交互效果
1 做交互效果(点击一个按钮,弹出某个框或者跳转到某个页面)其实就是若干个PPT页面,利用给按钮增加超链接或者动作 达到点击一个按钮 跳转到另外一个PPT的效果. 2 选择一个形状组件(或者其他组件) ...