1.最简单的不考虑平台性等等问题
HANDLE libHandle; char dllbuf[22] = "user32.dll"; libHandle = LoadLibraryA(dllbuf); __asm { sub esp, 0x440 xor ebx, ebx //下面3条代码是为了构建字符串, ebx=0是为了作为字符串的NULL结尾字节, 此时通过esp作为字符串参数传递目标函数即可使用freesec这个字符串 push ebx push 0x636573 // sec push 0x65657266 //free mov eax,esp push ebx //0 push eax //字符串freesec push eax push ebx //0 mov eax ,0x77d804ea //MessageBoxA的地址 call eax //MessageBoxA 调用 push ebp mov eax,0x7c81cdda //exit()的地址 call eax //退出程序 } return 0;
这里主要学会了如何在shellcode中构建字符串和使用字符串
2.在xp及2003 server 动态定位API
不同的操作系统版本,打补丁的版本会使得各个系统dll中的代码,数据等信息. 所以函数地址直接硬编码肯定不行.需要动态获得API地址
获取kernel32.dll的API地址原理:
(1) fs段寄存器(段选择子)可以找到当前线程环境块 TEB
(2) 线程环境块偏移0x30的地方存放着指向进程环境块PEB的指针
(3) 进程环境块偏移0xc地方存放指向PEB_LDR_DATA结构体指针,其中存放着已经被进程装载的动态链接库的信息
(4) PEB_LDR_DATA结构偏移0x1c存放着指向模块初始化链表头指针InInitializationOrderModuleList
(5) InInitializationOrderModuleList中按顺序存放着pe装入时模块信息. 第一个是ntdll.dll , 第2个是kernel32.dll. 然后在偏移0x8字节使其加载基址
(6) 然后通过pe文件基址找到导出表即可
通过导出表获取API地址需要对API名称字符串进行对比,但是直接使用字符串对比会使得shellcode过于长. 因此考虑使用计算hash值进行比较.如下代码
unsigned int hash(char* funcName)
{
unsigned int val = 0;
while (*funcName)
{
val = ((val << 16) | (val >> 16)) + (unsigned int)*funcName; //对函数名每个字符的ascii值循环右移16bit,注意设计的hash函数计算结果
不能存在某个字节是0, 因为 00会截断字符串
funcName++;
}
return val;
}
移位指令和累加都比较简单,只要先计算好想要加载的api的hash值,然后通过对导入表中的api计算其hash值,最后对2者相比即可
经计算得:
MessageBoxA 's hash is 01F90236
LoadLibraryA 's hash is 02350261
ExitProcess 's hash is 02340245
然后汇编代码:
__asm { cld; clear DF flag //压栈时注意将同一个dll中的API放在一起进行压栈,这样可以方便切换dll基址,节约代码量 //LoadLibraryA必须先获取到并保存,后面加载模块时还会用到 push 0x01F90236; MessageBoxA's hash push 0x02340245; ExitProcess's hash push 0x02350261; LoadLibraryA's hash mov esi, esp;获取hash值地址到esi lea edi, [esi - 0xc]; //现有的重要寄存器:esi指向3个API的hash的地址,edi指向hash值的地址下方0xc处目的是为了保存3个 //API的真正地址 xor ebx, ebx; ebx = 0 mov bh,0x04 sub esp, ebx; 开辟栈空间 mov bx, 0x3233 push ebx push 0x72657375 //"user32" push esp xor edx, edx//edx=0 mov ebx, fs:[edx + 0x30];ebx指向peb mov ecx,[ebx+0x0c] mov ecx,[ecx+0x1c]//ecx为模块链表头指针,即ecx值为ntdll所属的链表节点的地址 mov ecx,[ecx] //链表节点前4字节存储下一个节点地址,赋值给了ecx mov ebp,[ecx+0x08]//偏移8处为该模块的基址即kernel32.dll基址 //此时就esp,ebp,esi,edi有用,其他寄存器可以修改.edx=0,可以直接用 find_lib_functions: lodsd //将dword大小的数据从esi写到eax中,此时df是置位的,并增加esi指向下一个dword.实际上就是一个一个地获取APIhash cmp eax, 0x01F90236 //以之前对APIhash压栈的模块顺序的第一个API进行比较,方便模块切换 jnz find_functions //下面2个xchg指令是交换2个寄存器值,当执行到该处时eax是当前要获取的API,保存到ebp中防止被loadlibrary //函数修改,因为执行到这里说明要切换dll了,所以ebp刚好又通过返回值更新了 xchg eax,ebp call [edi-0x8] xchg eax,ebp //此时,esp,ebp,eax,edi,esi是重要寄存器 find_functions: //此时各寄存器作用:eax=目标函数名,ebx无,ecx无,edx=0,ebp=dll基址,esi=hash,edi=获取的API地址 pushad mov eax,[ebp+0x3c]//eax=peHead=nthead mov ecx,[ebp+eax+0x78]//nthead+0x78=导出表rva add ecx,ebp //这里在内存中,可以直接用基址加上rva得到导出表绝对地址 mov ebx, [ecx+0x20] //得到存储名字的数组的rva,数组存的是名字字符串地址rva add ebx,ebp //得到绝对地址 xor edi,edi //edi作为该数组索引,ebx作为数组基址,esi存储API字符串,eax作为字符串的单个字符串,edx作为临时的hash next_function_loop: inc edi mov esi,[ebx+edi*4] //将地址rva存放到esi add esi,ebp //获取绝对地址 cdq //因为eax在这里不会>f0000000 所以edx的每一bit都是0.其实就是将edx赋值为0,因为 //后续代码会将edx作为hash的临时保存, 所以在计算hash前需要清0 hash_loop: movsx eax,byte ptr [esi] cmp al,ah //当到字符串结尾时al=0,ah此时是0,就进行比较,否则继续计算hash jz compare_hash//edx保存着hash值 ror edx ,16 add edx,eax inc esi jmp hash_loop compare_hash: cmp edx,[esp+0x1c] jnz next_function_loop //找到该函数后进行的操作 mov ebx,[ecx+0x24] //将ebx指向导出索引表即orginal table add ebx, ebp mov di, [ebx + 2 * edi]//找到对应序号 mov ebx, [ecx + 0x1c] //将ebx指向导出地址表,寻找真正的函数地址 add ebx, ebp add ebp, [ebx + 4 * edi]//将函数地址存放到ebp xchg eax, ebp //又存放到eax pop edi//弹出原来的edi,即获取到的api地址缓存. 因为pushad将edi最后压栈的 stosd//将eax赋值给edi并且edi增加一个dword大小 push edi//重新保存edi popad cmp eax, 0x01F90236 //判断是否是最后一个要获取的函数,是的话就进行函数调用了 jne find_lib_functions function_call: xor ebx, ebx push ebx push 0x636573 // sec push 0x65657266 //free mov eax, esp push ebx push eax push eax push ebx call [edi-0x4] push ebx call [edi-0x8] nop nop }
二进制数据为:
"xFCx68x36x02xF9x01x68x45x02x34x02x68x61x02x35x02"
"x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"
"x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"
"x49x1Cx8Bx09x8Bx69x08xADx3Dx36x02xF9x01x75x05x95"
"xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"
"x20x03xDDx33xFFx47x8Bx34xBBx03xF5x33xD2x0FxBEx06"
"x3AxC4x74x08xC1xCAx10x03xD0x46xEBxF1x3Bx54x24x1C"
"x75xE3x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDD"
"x03x2CxBBx95x5FxABx57x61x3Dx36x02xF9x01x75xA8x33"
"xDBx53x68x73x65x63x00x68x66x72x65x65x8BxC4x53x50"
"x50x53xFFx57xFCx53xFFx57xF8"
测试程序:
// ts.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" char shellcode[]= "xFCx68x36x02xF9x01x68x45x02x34x02x68x61x02x35x02" "x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53" "x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B" "x49x1Cx8Bx09x8Bx69x08xADx3Dx36x02xF9x01x75x05x95" "xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59" "x20x03xDDx33xFFx47x8Bx34xBBx03xF5x33xD2x0FxBEx06" "x3AxC4x74x08xC1xCAx10x03xD0x46xEBxF1x3Bx54x24x1C" "x75xE3x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDD" "x03x2CxBBx95x5FxABx57x61x3Dx36x02xF9x01x75xA8x33" "xDBx53x68x73x65x63x00x68x66x72x65x65x8BxC4x53x50" "x50x53xFFx57xFCx53xFFx57xF8";
int _tmain(int argc, _TCHAR* argv[])
{
__asm
{
xor eax,eax
lea eax,[eax+shellcode]
push eax
ret
}
return 0;
}
如图:
解决shellcode在高版本windows平台系统兼容性
将上述shellcode在win8.1 64机器上无法运行
经过调试发现: (在cdq处下条件记录断点)
00401079 COND: func name= = LCIDToLocaleName
00401079 COND: func name= = LCMapStringA
00401079 COND: func name= = LCMapStringEx
00401079 COND: func name= = LCMapStringW
00401079 COND: func name= = LeaveCriticalPolicySectionInternal
00401079 COND: func name= = LeaveCriticalSection
00401079 COND: func name= = LeaveCriticalSectionWhenCallbackReturns
00401079 COND: func name= = LoadAppInitDlls
00401079 COND: func name= = LoadLibraryExA
00401079 COND: func name= = LoadLibraryExW
00401079 COND: func name= = LoadResource
00401079 COND: func name= = LoadStringA
00401079 COND: func name= = LoadStringBaseExW
00401079 COND: func name= = LoadStringByReference
00401079 COND: func name= = LoadStringW
00401079 COND: func name= = LocalAlloc
有这个LoadLibraryExA 而没有了LoadLibraryA
因此将代码中的LoadLibraryA 的hash改为LoadLibraryExA的hash 并修改传递的参数
修改后代码如下:
__asm { cld; clear DF flag //压栈时注意将同一个dll中的API放在一起进行压栈,这样可以方便切换dll基址,节约代码量 //LoadLibraryA必须先获取到并保存,后面加载模块时还会用到 push 0x01F90236; MessageBoxA's hash push 0x02340245; ExitProcess's hash //push 0x02350261; LoadLibraryA's hash push 0x02AD02A6; LoadLibraryExA's hash mov esi, esp; 获取hash值地址到esi lea edi, [esi - 0xc]; //现有的重要寄存器:esi指向3个API的hash的地址,edi指向hash值的地址下方0xc处目的是为了保存3个 //API的真正地址 xor ebx, ebx; ebx = 0 mov bh, 0x04 sub esp, ebx; 开辟栈空间 xor edx, edx//edx=0 mov bx, 0x3233 push ebx push 0x72657375 //"user32" mov ebx,esp push edx //LoadLibraryExA 第3个参数, 设为0 push edx //LoadLibraryExA 第2个参数, 设为0 push ebx //LoadLibraryExA 第1个参数, 即为user32.dll mov ebx, fs:[edx + 0x30]; ebx指向peb mov ecx, [ebx + 0x0c] mov ecx, [ecx + 0x1c]//ecx为模块链表头指针,即ecx值为ntdll所属的链表节点的地址 mov ecx, [ecx] //链表节点前4字节存储下一个节点地址,赋值给了ecx mov ebp, [ecx + 0x08]//偏移8处为该模块的基址即kernel32.dll基址 //此时就esp,ebp,esi,edi有用,其他寄存器可以修改.edx=0,可以直接用 find_lib_functions: lodsd //将dword大小的数据从esi写到eax中,此时df是置位的,并增加esi指向下一个dword.实际上就是一个一个地获取APIhash cmp eax, 0x01F90236 //以之前对APIhash压栈的模块顺序的第一个API进行比较,方便模块切换 jnz find_functions //下面2个xchg指令是交换2个寄存器值,当执行到该处时eax是当前要获取的API,保存到ebp中防止被loadlibrary //函数修改,因为执行到这里说明要切换dll了,所以ebp刚好又通过返回值更新了 xchg eax, ebp call[edi - 0x8] xchg eax, ebp //此时,esp,ebp,eax,edi,esi是重要寄存器 find_functions : //此时各寄存器作用:eax=目标函数名,ebx无,ecx无,edx=0,ebp=dll基址,esi=hash,edi=获取的API地址 pushad mov eax, [ebp + 0x3c]//eax=peHead=nthead mov ecx, [ebp + eax + 0x78]//nthead+0x78=导出表rva add ecx, ebp //这里在内存中,可以直接用基址加上rva得到导出表绝对地址 mov ebx, [ecx + 0x20] //得到存储名字的数组的rva,数组存的是名字字符串地址rva add ebx, ebp //得到绝对地址 xor edi, edi //edi作为该数组索引,ebx作为数组基址,esi存储API字符串,eax作为字符串的单个字符串,edx作为临时的hash next_function_loop : inc edi mov esi, [ebx + edi * 4] //将地址rva存放到esi add esi, ebp //获取绝对地址 cdq //因为eax在这里不会>f0000000 所以edx的每一bit都是0.其实就是将edx赋值为0,因为 //后续代码会将edx作为hash的临时保存, 所以在计算hash前需要清0 hash_loop : movsx eax, byte ptr[esi] cmp al, ah //当到字符串结尾时al=0,ah此时是0,就进行比较,否则继续计算hash jz compare_hash//edx保存着hash值 ror edx, 16 add edx, eax inc esi jmp hash_loop compare_hash : cmp edx, [esp + 0x1c] jnz next_function_loop //找到该函数后进行的操作 mov ebx, [ecx + 0x24] //将ebx指向导出索引表即orginal table add ebx, ebp mov di, [ebx + 2 * edi]//找到对应序号 mov ebx, [ecx + 0x1c] //将ebx指向导出地址表,寻找真正的函数地址 add ebx, ebp add ebp, [ebx + 4 * edi]//将函数地址存放到ebp xchg eax, ebp //又存放到eax pop edi//弹出原来的edi,即获取到的api地址缓存. 因为pushad将edi最后压栈的 stosd//将eax赋值给edi并且edi增加一个dword大小 push edi//重新保存edi popad cmp eax, 0x01F90236 //判断是否是最后一个要获取的函数,是的话就进行函数调用了 jne find_lib_functions function_call : xor ebx, ebx push ebx push 0x636573 // sec push 0x65657266 //free mov eax, esp push ebx push eax push eax push ebx call[edi - 0x4] push ebx call[edi - 0x8] nop nop }
win8.1 64位测试:
win7 64位机测试: