ShellCode瘦身的艺术0_HASH
写在前面的话:
前面几篇文章,我们介绍了如何获取kernerl32.dll导出函数地址的方法;
并在此基础上,编写了ShellCode,实现了动态加载DLL以及解析API地址;
但是,似乎还称不上Perfect,我们能够获取到LoadLibrary和GetProcAddress,事情就结束了吗?
我们仍然需要给他们push一些个参数,那些API的名字,占用了我们ShellCode的大部分空间;(如果API较多的话)
这使得我们的ShellCode看上去不那么美妙,因此,对API做HASH势在必行;
那也许有朋友会问:做了HASH,总有一处还原的地方吧,如果不还原,那程序里就一定有字符串存在;否则,GetProcAddress怎么玩呢?
也因此,我们对Kernel32.dll导出表的解析,就需要一般化一下了;让它不止适应于kernel32.dll,而是windows下的任何32位的PE文件;
(64位类似,解析PE,都一样,笔者就拿32位举例了,有兴趣的朋友也可以自行解析)
如果能够做到,那我们的HASH才会有意义,因为,ENT里就有API名字了;
因此,在开始HASH运算前,我们先来搞一下之前的那部分程序;
零:导出表一般化解析
0. 先来看下PE的DOS头结构
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
我们需要关注的是最后一个字段,这个里的内容是NT头的偏移,首先,看下,这个字段在本结构体的偏移60(0x3C)
也就是说,[BaseAddr+0x3C]就是e_lfanew的值,因此,NT头的首地址BaseAddr+e_lfanew;
那再看下NT头的结构:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
// NT头中的文件头20Byte
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
// NT头中的扩展头
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
我们要找什么呢,导出表的RVA,导出表是扩展头里的第0号元素;因此,计算出的数据目录表[导出表]相对NT头的偏移,就是0x78;
至此,我们通过分析DOS头和NT头结构,得到了下面的信息:
0、e_lfanew = [BaseAddr+0x3C]
1、NTStartVA:BaseAddr + e_lfnew
2、ExportStartRVA:[NTStartVA + 0x78]
3、ExportStartVA:BaseAddr + ExportStartRVA
到这一步,接下来就需要看下导出表的结构了
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
需要的字段,EAT/ENT/EOT,我们在上边的分析中,其实已经得到了这个导出表结构体的首地址了,就是ExportStartVA,那就简单了
4、EATRVA = [ExportStartVA + 0x1C] -> EAT = BaseAddr + EATRVA
5、ENTRVA = [ExportStartVA + 0x20] -> ENT = BaseAddr + ENTRVA
6、EOTRVA = [ExportStartVA + 0x24] -> EOT = BaseAddr + EOTRVA
至此,分析结束,开始编写代码;
一、代码(为了便于理解,咱们封装成一些裸函数)
0、获取基址
void __declspec(naked) GetKernelBase() {
_asm {
push ebp;
mov ebp, esp;
sub esp, 0x0C;
mov eax, fs:[0x30]; // PEB
mov eax, [eax + 0xC]; // LDR
mov eax, [eax + 0xC]; // InLoadOrderModuleList, exe
mov eax, [eax]; // nt.dll
mov eax, [eax]; // kernel32.dll
mov eax, dword ptr ds : [eax + 0x18]; // BaseAddr;
mov esp, ebp;
pop ebp;
ret;
}
}
1、解析导出表,部分关键代码(全部代码,考虑下,还是在我们写完HASH算法后贴出来)
_asm{
push ebp;
mov ebp, esp;
sub esp, 0x10;
push ebx;
push ecx;
push esi;
push edi;
...
mov [ebp - 0x4], eax; // [ebp - 0x4] -> BaseAddr
mov eax, [eax + 0x3C]; // e_lfanew
add eax, [ebp - 0x4]; // NTStartVA
mov eax, [eax + 0x78]; // ExportStartRVA
add eax, [ebp - 0x4]; // ExportStart_VA
mov ebx, [eax + 0x1C]; // EATRVA
add ebx, [ebp - 0x4]; // EAT
mov [ebp - 0x8], ebx; // [ebp - 0x8] -> EAT
mov ebx, [eax + 0x20]; // ENTRVA
add ebx, [ebp - 0x4]; // ENT
mov [ebp - 0xC], ebx; // [ebp - 0xC] -> ENT
mov ebx, [eax + 0x24]; // EOTRVA
add ebx, [ebp - 0x4]; // EOT
mov [ebp - 0x10], ebx; // [ebp - 0x10] -> EOT
...
pop edi;
pop esi;
pop ecx;
pop ebx;
mov esp, ebp;
pop ebp;
ret;
}
2、接下来就要考虑如何实现HASH算法了
要求:尽量简单,又不失功能;(不同的API的HASH碰撞几率越小越好,同时ShellCode里,要兼顾体积)
/*
* @1 API
* @2 Length
*/
void __declspec(naked) ApiHash() {
_asm {
push ebp;
mov ebp, esp;
sub esp, 0x8;
mov dword ptr[ebp - 0x4], 0x6B821B17; // Init Hash Value
mov dword ptr[ebp - 0x8], ; // Init Local Var
jmp short _begin; _loop:
mov eax, [ebp + 0x8]; // eax = srcApi
add eax, 0x1; // eax = srcApi + 1
mov[ebp + 0x8], eax; // srcApi++
mov ecx, [ebp - 0x8]; // ecx = i
add ecx, 0x1; // ecx += 1
mov[ebp - 0x8], ecx; // i++ _begin:
mov edx, [ebp - 0x8]; // edx = i
cmp edx, [ebp + 0xC]; // edx vs len
jnb short _end; // if (edx >= len) exit;
mov eax, [ebp - 0x4]; // eax = Hash
shl eax, 0x5; // eax = Hash << 5
mov ecx, [ebp + ]; // ecx = srcApi
movsx edx, byte ptr[ecx]; // edx = *srcApi
add eax, edx; // eax = Hash << 5 + *srcApi
mov ecx, [ebp - 0x4]; // ecx = Hash
shr ecx, 0x2; // ecx = Hash >> 2
add eax, ecx; // eax = Hash << 5 + *srcApi + Hash >> 2
xor eax, [ebp - 0x4];
mov[ebp - 0x4], eax; // Hash ^= (Hash << 5 + *srcApi + Hash >> 2);
jmp short _loop; _end:
mov eax, [ebp - 0x4]; // eax = Hash
mov esp, ebp;
pop ebp;
ret 0x8;
}
}
3、既然HASH算法也有了,在开始编写获取API的函数之前,先实现一个获取字符串长度的函数;
/*
* @ String
*/
void __declspec(naked) asmstrlen() {
_asm {
push ebp;
mov ebp, esp;
sub esp, 0x4;
mov dword ptr [ebp - 0x4], ;
jmp short _begin; _loop:
mov eax, [ebp + 0x8]; // eax = String
add eax, 0x1; // eax = String + 1
mov [ebp + 0x8], eax; // String++
mov ecx, [ebp - 0x4]; // ecx = i
add ecx, 0x1; // ecx += 1
mov [ebp - 0x4], ecx; // i++ _begin:
mov ecx, [ebp + ]; // ecx = String
movsx edx, byte ptr [ecx]; // edx = *String
cmp edx, ;
je _end;
jmp _loop; _end:
mov eax, [ebp - 0x4]; // eax = len
mov esp, ebp;
pop ebp;
ret 0x4;
}
}
4、接下来,就要编写通过HASH获取API地址的函数了
/*
* @1 BaseAddr
* @2 HASH
*/
void __declspec(naked) GetHASHAPIAddr() {
_asm {
push ebp;
mov ebp, esp;
sub esp, 0x14;
push esi;
push edi; mov eax, [ebp + ]; // BaseAddr
mov [ebp - 0x4], eax;
mov eax, [eax + 0x3C]; // e_lfanew
add eax, [ebp - 0x4]; // NTStartVA
mov eax, [eax + 0x78]; // ExportStartRVA
add eax, [ebp - 0x4]; // ExportStart_VA
mov ebx, [eax + 0x1C]; // EATRVA
add ebx, [ebp - 0x4]; // EAT
mov [ebp - 0x8], ebx; // [ebp - 0x8] -> EAT
mov ebx, [eax + 0x20]; // ENTRVA
add ebx, [ebp - 0x4]; // ENT
mov [ebp - 0xC], ebx; // [ebp - 0xC] -> ENT
mov ebx, [eax + 0x24]; // EOTRVA
add ebx, [ebp - 0x4]; // EOT
mov [ebp - 0x10], ebx; // [ebp - 0x10] -> EOT xor ebx, ebx;
mov eax, [eax + 0x18]; // NumOfNames
mov [ebp - 0x14], eax;
cld; _ENT_FIND:
mov esi, [ebp - 0xC]; // ENTStartVA
mov esi, [esi + * ebx]; // ENTContentRVA
add esi, [ebp - 0x4]; // ENTContentVA
push esi;
push esi;
call asmstrlen;
pop esi;
push eax;
push esi;
call ApiHash;
mov edi, [ebp + 0xC]; // HASH
cmp eax, edi;
je _ENT_OK;
inc ebx;
mov eax, [ebp - 0x14];
dec eax;
mov [ebp - 0x14], eax;
cmp eax, ;
jg _ENT_FIND;
jmp _ENT_END; _ENT_OK:
mov ecx, [ebp - 0x10]; // EOTStartVA
mov ecx, [ecx + * ebx];
and ecx, 0xFFFF;
mov esi, [ebp - 0x8]; // EATStartVA
mov eax, [esi + * ecx]; // EAT Address RVA
add eax, [ebp - 0x4]; // EAT Address VA _ENT_END:
pop edi;
pop esi;
mov esp, ebp;
pop ebp;
ret 0x8;
}
}
我们只需要事先准备好需要的API的HASH值,就可以了,下面让我们来测试下;
5、测试
int main(int argc, char** argv) {
DWORD LoadLibAddr = ;
_asm {
call GetKernelBase;
push 0x28182EF6; // LoadLibrayA HASH
push eax;
call GetHASHAPIAddr;
mov LoadLibAddr, eax;
}
printf("LoadLibrary[0x%X]\n", LoadLibAddr);
getchar();
return ;
}

我们在调试器中输入这个地址:

可以看到,获取到了这个函数的地址;
获取有同学会说,这个是在kernel32.dll里的,其他dll里的函数也可以吗;当然了,看我们的GetHashAPIAddr参数就知道了;
来代码吧,搞一个MessageBox的函数,这个是在user32.dll里的,见代码,运行后会弹框,证明就成功了;
int main(int argc, char** argv) {
char srcDll[] = "user32.dll";
DWORD LoadLibAddr = ;
_asm {
call GetKernelBase;
push 0x28182EF6; // LoadLibrayA HASH
push eax;
call GetHASHAPIAddr;
mov LoadLibAddr, eax;
push esi;
mov esi, eax;
lea eax, srcDll;
push eax;
call esi;
push 0x564B6854; // MessageBoxA HASH
push eax;
call GetHASHAPIAddr;
push ;
push ;
push ;
push ;
call eax;
}
printf("LoadLibraryA[0x%X]\n", LoadLibAddr);
getchar();
return ;
}

至此,我们的API算是都准备好了,通过实现HASH算法,我们去掉了占用体积过大的API字符串,瘦身的目的达到了;
在后续的文章中,笔者将带领大家一起分析ShellCode中的截断问题,敬请期待;
ShellCode瘦身的艺术0_HASH的更多相关文章
- iOS UIViewController的瘦身计划
代码的组织结构,以及为何要这样写. 那些场景适合使用子控制器,那些场景应该避免使用子控制器? 分离UITableView的数据源和UITableViewDataSource协议. MVVM的重点是Vi ...
- Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)
Android XML shape 标签使用详解 一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...
- Android APK瘦身之Android Studio Lint (代码审查)
******** ******** 第一部分: 瘦身内容介绍 ******** ******** 项目新版本的迭代接近尾声, 因为历史累积问题, 导致有很多无效的资源让已经臃肿的APK变得更肿, 因此 ...
- 【直播】APP全量混淆和瘦身技术揭秘
[直播]APP全量混淆和瘦身技术揭秘 近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,通过逆向手段造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户苦不堪 ...
- APK瘦身记,如何实现高达53%的压缩效果
作者:非戈@阿里移动安全 1.我是怎么思考这件事情的 APK是Android系统安装包的文件格式,关于这个话题其实是一个老生常谈的题目,不论是公司内部,还是外部网络,前人前辈已经总结出很多方法和规律. ...
- iOS可执行文件瘦身方法
缩减iOS安装包大小是很多中大型APP都要做的事,一般首先会对资源文件下手,压缩图片/音频,去除不必要的资源.这些资源优化做完后,我们还可以尝试对可执行文件进行瘦身,项目越大,可执行文件占用的体积越大 ...
- 清理iOS工程里无用的图片,可瘦身ipa
工程在经过多人后,往往会出现较多的垃圾,导致打包出来的ipa文件偏大,有时候我们会通过清理代码来给程序瘦身,而瘦身ipa效果明显的,主要通过清理程序里的无用图片. 推荐一个清理图片的应用 https: ...
- iOS - Bitcode App 瘦身中间码
1.Bitcode 随着 Xcode7 的发布,Apple 提供了一项新的技术来支持 App 瘦身功能,那就是 Bitcode. 1.BitCode 是什么 Bitcode is an interme ...
- iOS架构师之路:控制器(View Controller)瘦身设计
前言 古老的MVC架构是容易被iOS开发者理解和接受的设计模式,但是由于iOS开发的项目功能越来越负责庞大,项目代码也随之不断壮大,MVC的模糊定义导致我们的业务开发工程师很容易把大量的代码写到视图控 ...
随机推荐
- ubuntu上修改root密码
ubuntu上修改root密码 author: headsen chen 2017-10-12 10:49:28 个人原创,转载请注明作者,出处. sudo passwd 两次输入想设置的r ...
- 教你快速打造PHP MVC框架
简介 MVC框架在现在的开发中相当流行,不论你使用的是JAVA,C#,PHP或者IOS,你肯定都会选择一款框架.虽然不能保证100%的开发语言都会使用框架,但是在PHP社区当中拥有最多数量的MVC框架 ...
- 利用sfc文件构建网络渗透
收集哈希 SCF(Shell命令文件)文件可用于执行一组有限的操作,例如显示Windows桌面或打开Windows资源管理器,这并不是什么新鲜事.然而,一个SCF文件可以用来访问一个特定的UNC路 ...
- Eclipse CDT开发环境搭建及问题记录(Windows)
这两天在整Eclipse,在此记录过程中遇到的一些问题. 1.安装JDK,配置系统路径: 2.下载Eclipse 直接访问Eclipse官网(https://www.eclipse.org/downl ...
- 笔记:Spring Cloud Hystrix 服务容错保护
由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加 ...
- 在RE了16次之后,没想到还可以这样Runtime error
这是POJ: RE的原因: 比如: int b=2147483647; for(int i=0;i<=b;++i){ .... } 应该懂了吧, 2147483647是int能表示的最大整数 解 ...
- 关于 Git使用的全面总结 —— 致敬Git之父Linux
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px ".PingFang SC"; color: #454545 } p.p2 ...
- java.lnag.Throwable详细解读
public class Throwable extends Object implemnts Serializable Throwable类是所有错误或异常的超类.只有当对象是此类(或其中之 ...
- JS获得一个对象的所有属性和方法
function displayProp(obj){ var names=""; for(var name in obj){ names+=name+": "+ ...
- centos 7.0远程登录
http://blog.csdn.net/e1219092641/article/details/79586476 linux在虚拟机上操作也是有许多不便之处的,但是远程登录的使用可以使操作简单不少, ...