2014年7月22日 星期二

备忘:关于VB程序报错Out of memory

msvbvm60.dll写的很屎,有一个报错函数写死了报Out of memory,今天就遇到的情况明明是hook的伪SetFilePointer写错了,在某些情况成功操作但是函数返回失败,结果msvbvm60.dll搞了个Out of memory,真够坑爹的。
所以,VB6程序报错Out of memory等于报错Error,除了说明发生了错误之外什么都不能说明 ,memory什么的就当它是个屁好了。

不用任何API获取本可执行文件的ImageBase(dll hModule)

一句话:
extern "C" IMAGE_DOS_HEADER __ImageBase;
这两天读msvcrt源码所得,以后dll获取自身的hmodule再也不用VitualQuery了

2014年7月20日 星期日

VS2013写的dll被内存加载失败原因一例

  在公司写的某dll终于完成了,和公司的壳一同使用时,公司的一种壳可以稳定的从内存加载使用我写的dll,而另一种时好时坏(大多数情况都无 法正常加载),在我来这里工作之前,这两个壳在内存加载他们使用低版本VS编译出的dll时都可以正常工作,我因为使用了一些C++11语法特性,实在不 想做降版这蛋疼事,调试分析了这其中的原因。
  经过一番调试,终于把前因后果捋顺了:
· 壳->DLLEntryPoint:走到_DllMainCRTStartup
· _DllMainCRTStartup->_CRT_INIT:初始化CRT环境
· _CRT_INIT->_cinit:这还真不知道是初始化啥
· 其中_cinit中有段代码是这样写的:
if (_FPinit != NULL &&
     _IsNonwritableInCurrentImage((PBYTE)&_FPinit))
{
      //这里代码具体记不清楚了,是调用了一个函数初始化浮点数运算相关环境
}

代码意思比较好懂,如果_FPinit不为零同时这个变量在当前映像中是不可写入的就初始化浮点数运算环境。而问题就出在_IsNonwritableInCurrentImage的判断上了:
BOOL __cdecl _IsNonwritableInCurrentImage(char *pTarget)
{
  _IMAGE_SECTION_HEADER *pTargetSectoinHeader; 
  BOOL bResult = FALSE;
  if ( _ValidateImageBase((char *)0x10000000)
&& (pTargetSectoinHeader = 
  _FindPESection((char *)0x10000000, (unsigned int)(pTarget - 0x10000000))) != 0 )
  {
    bResult = ~(pTargetSectoinHeader->Characteristics >> 31) & 1;
  }
  return bResult;
}


这个函数先调用_ValidateImageBase判断了当前映像基址是否为合法的PE文件头,然后调用_FindPESection找出变量所在的区段的区段头,检查区段头中记录的该区段是否具有写入权限,返回结果。代码中写死的0x10000000地址都会在dll加载时系统通过重定位表将这些地址改写为映像基址。
但是在_ValidateImageBase判断头两个字节是否为MZ时程序就非法内存访问然后崩溃了。
经过了与维护壳的同事沟通确认,产生问题的壳在最初分析完我的dll之后就把PE头抹掉了,使用了一个自定义的结构体代替PE头记录了部分PE头内的信息供内存加载dll初始化时使用,在为我的dll代码申请内存空间时根本就没有留出PE头的空,当然更没有把PE头还原回去。
内存空间就成了这样:
未分配的内存,本应为PE头
为DLL申请的内存
↑ImageBase
于是经过重定位表重定位后传递给_ValidateImageHeader的映像基址就指向了一处非法地址,最终产生了非法内存访问。在极其偶然的情况下该地址可能指向了另一块内存区域的一部分,指向的内容正好不是MZ开头,而这个dll又凑巧不需要初始化浮点数运算环境,才会出现偶尔可以正常启动的情况。
至此这个问题的前因后果已经搞明白,具体该如何解决还等和领导沟通后再定吧。
--8:28 PM 7/21/2014
解决方案:
方法一(最省事相对稳定):由于_IsNonwritableInCurrentImage总共就判断两个变量,所以可以根据自身程序情况写个代替函数写死了,修改工程设置人为规定EntryPoint,调用_CRT_INIT初始化之前修改内存权限把自己写的函数memcpy过去。
方法二(相对省事可能不稳定):修改工程设置人为规定EntryPoint,然后参照IDA F5和VS安装目录下的crt源代码,写自己的_DllMainCRTStartup、_CRT_INIT、_cinit。不稳定因素有二:1.由于msvcrt.lib有两个obj都有某全局变量,所以实现_CRT_INIT时对着两个全局变量是没法初始化了2.可能正是由于不稳定因素1,调用初始化环境的函数会失败。
方法三(最稳定最费时):搞个自己的msvcrt.lib

2014年7月2日 星期三

Hook CloseHandle时遇到程序退出时卡死的问题原因及解决方案

  实际上这个问题我很久之前就遇到过了,在Hook CloseHandle函数的情况下,点击关闭按钮后程序要等一段时间才能够关闭,当初只当是虚拟机性能差造成的延迟,直到前几天我在新公司写Hook代码在实机上测试才发现问题并不是我之前认为的那样。

问题现象:
  • 在Hook CloseHandle函数的情况下,点击关闭按钮后要等一段时间进程才能够退出。
问题原因:
  • 在程序退出的过程中,全局对象的生存期已经结束,系统在进行进一步收尾工作时调用了CloseHanle,这时在伪函数内访问了已经销毁的全局对象,导致异常产生。
解决方案:
  • 动态申请在伪函数内需要访问到的全局对象,将对象保存在堆内。
  此时对象保存在堆内而非栈内,在程序退出全局变量生存期结束时不会调用析构函数。同时,虽然用于保存该对象地址的指针/引用虽然也超过生存期,但是由于没有被复写,也不存在析构函数,所以可以访问。

分析过程:
  首先尝试在进程等待退出时间段内使用Process Explorer查看该进程各个线程的调用堆栈情况,初步判断原因。
  现象:一用Process Explorer查看该进程线程情况,一切换到该进程属性的Threads标签页程序立刻退出。
  现象分析:Process Explorer查询线程堆栈调用了dbghelp.dll内相关函数,可能程序遇到的情况如果不挂调试器就不继续执行了。
  尝试:先用Process Explorer查看该进程属性的Threads项,关闭程序,让异常现象产生
  现象:从调用堆栈可以看出,现场内存在未处理的异常。
  猜测:【猜测内容同问题原因】(运气不错,一下猜中了)
  编写粗糙的测试代码:
  BOOL bDestoryed = FALSE;
  Class fdsa{
  public:
    fdsa(){};
    ~fdsa(){bDestoryed = TRUE};
  }
  fdsa xxx;
    同时在CloseHandle的伪函数内判断bDestoryed是否被赋值为TRUE。

  最终确定猜测成立。

2013年11月15日 星期五

使用Greasemonkey脚本彻底摆脱BT工厂下载弹窗

今天有空,所以更新一下多年没有动过的博客。
事情是这样的……
正是因为今天有空,所以我为了陶冶情操,文化交流网站‘BT工厂’看了看最近更新的新的文化交流内容……
然后呢,我一如既往的点开了很多很多的文化交流种子下载页……
但是把这些下载页的种子全都下载下来是一件很痛苦的事情!
来看一下代码:
<form action="../down.php" method="post">
  <input type="hidden" value="torrent" id="type" name="type" />
  <input type="hidden" value="#######" id="id" name="id" />
  <input type="hidden" value="#######" id="name" name="name" />
  <input type="submit" value="  下 载 /  down  " onclick="openpage()" class="round_box" id="down_btn"  />
</form>
可以看到,这个下载按钮被点击时不但会提交表单开始下载文件,同时还会触发openpage函数。
这个函数定义如下: 
function openpage() {
var rand=Math.floor(Math.random()*2+1);
switch(rand)
{
 case 1:
 window.open('http://??.?????.biz/','','scrollbars=1,resizable=1').blur();
 break;
 case 2:
 window.open('http://??.?????.info/','','scrollbars=1,resizable=1').blur();
 break;
 default:
 break;
 }
window.focus(); 
}
 
这才是最蛋疼的!每下载一个种子文件都要经过如下操作:
1点击一下下载按钮
2关闭弹出来的网站主页
3下载文件
4关闭网页

如果只是一个种子还好,但是我每次可都是要下载大量种子的啊!折腾不起啊啊啊啊!
然后我就去下载了greasemonkey插件然后开始折腾用户js脚本……
话说firefox这一点就不如换芯之前的opera方便,opera原生支持User CSS和User JS! 

解决弹窗的话很容易,只需要一句:
document.getElementById("down_btn").onclick = null;
 
好吧,即便如此还是太麻烦了…… 
毕竟有那么多种子等着我去下载学习呢,要是不用点击下载按钮就好了……
这也好说,这么一句就够了!
document.getElementsByTagName("form")[0].submit();
嗯…… 
这下好多了,尤其是我还在FireFox里设置了种子文件默认是保存操作,省得我一下一下点了。

不过好像还是有点不爽啊,既然都自动下载了,
那么下载了之后我又何必非要很傻逼的去手动关掉那些下载页!我想要它自动关掉!
于是就有了如下最终版代码:
// ==UserScript==
// @name        AntiBTGCPop&AutoDownload
// @namespace   wye-ANGER.blogspot.com
// @description AntiBTGCPop & AutoDownload
// @include     http://www3.*domn.com/*/file.php/*.html
// @include     http://www3.*down.info/*/file.php/*.html
// @include     http://www3.*down.com/*/file.php/*.html
// @version     1
// @grant       none
// ==/UserScript==

document.getElementsByTagName("form")[0].submit();
document.getElementsByTagName('html')[0].innerHTML
    = document.getElementsByTagName('form')[0].outerHTML;
var http_request = new XMLHttpRequest();
document.getElementById("down_btn").onclick = null;

function onResponse()
{
    if(http_request.readyState!=4) return;
    if(http_request.status!=200)
    {
        alert("Problem retrieving XML data");
        return;
    }
    //assume browser cached torrent
    setTimeout("window.close()",1000);
}

var nameval = document.getElementById("name").value;
var idval = document.getElementById("id").value;
var arg="type=torrent" +"&name="+nameval+"&id="+idval;

http_request.onreadystatechange=onResponse;
http_request.open('POST',"../down.php", true);
http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http_request.send(arg);

这个脚本需要配合两项FireFox来使用
1.about:config中的dom.allow_scripts_to_close_windows需要设置为true以允许js关闭窗口
2.火狐应设置自动保存种子文件而不询问(没试过,或许不这么设置也可以用)
使用这个脚本之后,只需要按住Ctrl点击下载页连接,下载页在后台打开后会自动开始下载,然后自动关闭下载页!
这下终于可以痛痛快快的进行文化交流了~
 
找脚本的同学看到这里就够了,如果对代码还有点兴趣可以继续看。 
 
稍微解释一下上面的脚本吧。
一开始先对form进行submit()先通过能触发下载的正规渠道发出请求。
然后问题又来了,从浏览器发出请求到服务器完成响应的间隔时间是不可预测的!
如果窗口关早了,服务器还没来得及响应请求,那文件都没来得及开始下载。 
对于BT工厂这样的文化交流网站更是如此!慢起来的时候那真是^$#&*@(&#$*@)&$*#@!
 
那么就需要简单的判断一下服务器是否已经响应了下载请求。
对此,我的解决方案是先调用submit()走正规途径提交表单。
然后通过XMLHttpRequest提交一个同样的表单。 
这时假设这两次提交表单对应的HTTP请求被浏览器缓存优化,
认定多出来的一次请求不会造成太多的网络负担(本来一个种子文件也不会造成多少负担)。 
假定等后提交的请求服务器完成全部响应时,前一个通过submit()发送的请求已经至少完成了初步的响应。
那么此时关闭窗口应该至少浏览器已经开始下载了。
 
至此,大功告成~