<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ftofficer&#124;张聪的blog &#187; windows</title>
	<atom:link href="http://blog.ftofficer.com/tag/windows/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.ftofficer.com</link>
	<description>A Newbie on the Way</description>
	<lastBuildDate>Sun, 12 Dec 2010 14:35:49 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<cloud domain='blog.ftofficer.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
		<item>
		<title>谈一个Kernel32当中的ANSI到Unicode转换的问题</title>
		<link>http://blog.ftofficer.com/2010/06/issue-in-kernel32-ansi-to-unicode-translation-and-cache-reentrant/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=rss</link>
		<comments>http://blog.ftofficer.com/2010/06/issue-in-kernel32-ansi-to-unicode-translation-and-cache-reentrant/#comments</comments>
		<pubDate>Thu, 17 Jun 2010 15:33:59 +0000</pubDate>
		<dc:creator>Zhang Cong</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[hook]]></category>
		<category><![CDATA[reentrant]]></category>
		<category><![CDATA[windows]]></category>

		<guid isPermaLink="false">http://blog.ftofficer.com/?p=10434</guid>
		<description><![CDATA[【这篇文章就是我在twitter上说过的那篇文章，早已写好，但由于一些原因压到现在才发表，深表歉意】 众所周知，Windows的几乎所有带有字符串参数的API都是有W和A两个版本，分别对应于Unicode和ANSI版本。同时，Windows内部是使用Unicode的，因此所有的A版本的API函数都是实际上调用了一次ANSI到Unicode的字符集转换之后，再调用Unicode版本的函数。 你真的清楚这中间的这一步转换吗？普通的工程师确实不需要了解太深刻，但是我们是做安全软件的，安全软件的一个基本实现机制就是Hook，就是在原本的API调用路径上面拐一个弯，改变原本的API执行流程，以便在其中插入安全检查等逻辑。 这当中，如果没有对于所要Hook的函数的深刻理解，也许会有很多看似灵异的事件出现。在我们的代码当中，我们就使用了Hook的机制，然后很幸运的（很不幸？），遇到了这样一个灵异问题： 在测试当中，发现某个软件一旦被我们Hook，就总是崩溃。必现的崩溃总是很容易解决的定位的，很快，我们发现崩溃来源于下面一个调用序列： HMODULE hMod = LoadLibraryA("Kernel32"); PROC pfxIsWow64Process = GetProcAddress(hMod, "IsWow64Process"); 对LoadLibrary的调用返回没有检查返回值，进而继续调用GetProcAddress。那么，当hMod = NULL的时候，GetProcAddress并不检查hMod是否是0，而直接试图去寻找其中的导出表，于是导致了非法的内存访问。 但是，这里的代码当中，调用的是LoadLibraryA(“kernel32&#8243;)，kernel32.dll作为系统的核心链接库，通常情况下几乎必然是已经加载到了进程空间当中，即使没有，加载此DLL也不应该失败。 但是调试的结果显示，这个返回值的确是NULL，看起来相当灵异。但是我们相信，计算机没有灵异事件。为了排错，我们祭出WinDbg和IDAPro，从汇编层面一步步跟踪LoadLibraryA函数，看看中间究竟发生了什么。经过漫长的错误定位工作，在此略过不表（关于整个排查过程可以写一篇专门的文章），我们终于发现了问题的所在。 没错，加载了Hook就会造成崩溃，因此崩溃的原因在于Hook。 我们使用Inline Hook技术修改了LoadLibrary函数的执行流程，使得在进行真正的DLL加载之前进行某些校验和检查的工作。LoadLibrary函数其实是一个函数族，包括LoadLibraryA，LoadLibraryW，LoadLibraryExA，和LoadLibraryExW。它们之间的调用关系如下图： 我们选择了在整个调用依赖树的最底端的LoadLibraryExW的入口点作为Hook点，当一个函数调用LoadLibraryExW的时候，其实是调用到我们的Hook函数，Hook函数再调用真正的LoadLibraryExW，经过Hook，一个典型的LoadLibraryA的调用流程如下图： 问题就出在了这个CheckLibrary函数里面。在这个函数里面，我们调用了一个公共模块当中的日志函数，这个日志函数的逻辑是，以追加模式打开一个日志文件，写入然后关闭。且不论这个日志函数这样做的合理性，先来看看这段代码： void Log(const char* msg) { FILE* pf = fopen(_g_log_file, "a"); fprintf(pf, "%s\n", msg); fclose(pf); } 非常直观的程序，怎么会带来问题呢？我们来继续分析fopen函数。fopen函数是c库提供的文件IO函数，最终调用到Windows API CreateFileA，然后CreateFileA会调用到CreateFileW。把这个调用关系加上，调用序列如下图所示： 在这个函数调用序列当中，存在两次ANSI 到 Unicode 的转换，即图中标记有五角星的两个地方。那么我们来看看两个转换的代码是什么样子的： LoadLibraryExA: CreateFileA 注意到其中高亮的部分了么？两个函数在进入之后，都调用了一个公共的函数 _Base8bitStringToStaticUnicodeString，从名字就可以看出来，这段代码负责将输入的ANSI字符串转换为Unicode。那么这个函数做了什么呢？跟进去看看（这个使用了Hex-Rays的反编译功能，看得更清楚一点）： 再仔细看给RtlAntiStringToUnicodeString这个函数传入的参数地址pUnicodeString，来自*MK_FP(__FS__, 24) + 3064，这是Hex-Rays的表示法，其实对应下面一串汇编序列（由于编译器的优化导致的乱序，重点看红线标出来的指令）： large [...]]]></description>
			<content:encoded><![CDATA[<p>【这篇文章就是我在twitter上<a href="http://proxy.ftofficer.com/dabr/status/12039195119" target="_blank">说过的那篇文章</a>，早已写好，但由于一些原因压到现在才发表，深表歉意】</p>
<p>众所周知，Windows的几乎所有带有字符串参数的API都是有W和A两个版本，分别对应于Unicode和ANSI版本。同时，Windows内部是使用Unicode的，因此所有的A版本的API函数都是实际上调用了一次ANSI到Unicode的字符集转换之后，再调用Unicode版本的函数。</p>
<p>你真的清楚这中间的这一步转换吗？普通的工程师确实不需要了解太深刻，但是我们是做安全软件的，安全软件的一个基本实现机制就是Hook，就是在原本的API调用路径上面拐一个弯，改变原本的API执行流程，以便在其中插入安全检查等逻辑。</p>
<p>这当中，如果没有对于所要Hook的函数的深刻理解，也许会有很多看似灵异的事件出现。在我们的代码当中，我们就使用了Hook的机制，然后很幸运的（很不幸？），遇到了这样一个灵异问题：</p>
<p>在测试当中，发现某个软件一旦被我们Hook，就总是崩溃。必现的崩溃总是很容易解决的定位的，很快，我们发现崩溃来源于下面一个调用序列：</p>
<p><code lang="c">HMODULE hMod = LoadLibraryA("Kernel32");<br />
PROC pfxIsWow64Process = GetProcAddress(hMod, "IsWow64Process");</code></p>
<p>对LoadLibrary的调用返回没有检查返回值，进而继续调用GetProcAddress。那么，当hMod = NULL的时候，GetProcAddress并不检查hMod是否是0，而直接试图去寻找其中的导出表，于是导致了非法的内存访问。</p>
<p>但是，这里的代码当中，调用的是LoadLibraryA(“kernel32&#8243;)，kernel32.dll作为系统的核心链接库，通常情况下几乎必然是已经加载到了进程空间当中，即使没有，加载此DLL也不应该失败。</p>
<p>但是调试的结果显示，这个返回值的确是NULL，看起来相当灵异。但是我们相信，计算机没有灵异事件。为了排错，我们祭出WinDbg和IDAPro，从汇编层面一步步跟踪LoadLibraryA函数，看看中间究竟发生了什么。经过漫长的错误定位工作，在此略过不表（关于整个排查过程可以写一篇专门的文章），我们终于发现了问题的所在。</p>
<p>没错，加载了Hook就会造成崩溃，因此崩溃的原因在于Hook。</p>
<p>我们使用<a href="http://bbs.pediy.com/showthread.php?t=59127" target="_blank">Inline Hook</a>技术修改了LoadLibrary函数的执行流程，使得在进行真正的DLL加载之前进行某些校验和检查的工作。LoadLibrary函数其实是一个函数族，包括LoadLibraryA，LoadLibraryW，LoadLibraryExA，和LoadLibraryExW。它们之间的调用关系如下图：</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode.png"><img class="aligncenter size-full wp-image-10457" title="LoadLibrary系列函数调用关系" src="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode.png" alt="" /></a></p>
<p>我们选择了在整个调用依赖树的最底端的LoadLibraryExW的入口点作为Hook点，当一个函数调用LoadLibraryExW的时候，其实是调用到我们的Hook函数，Hook函数再调用真正的LoadLibraryExW，经过Hook，一个典型的LoadLibraryA的调用流程如下图：</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-21.png"><img class="aligncenter size-full wp-image-10459" title="加了Hook之后的LoadLibrary函数族的调用关系" src="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-21.png" alt="" width="362" height="213" /></a></p>
<p>问题就出在了这个CheckLibrary函数里面。在这个函数里面，我们调用了一个公共模块当中的日志函数，这个日志函数的逻辑是，以追加模式打开一个日志文件，写入然后关闭。且不论这个日志函数这样做的合理性，先来看看这段代码：</p>
<p><code lang="c">void Log(const char* msg) {<br />
FILE* pf = fopen(_g_log_file, "a");<br />
fprintf(pf, "%s\n", msg);<br />
fclose(pf);<br />
}</code></p>
<p>非常直观的程序，怎么会带来问题呢？我们来继续分析fopen函数。fopen函数是c库提供的文件IO函数，最终调用到Windows API CreateFileA，然后CreateFileA会调用到CreateFileW。把这个调用关系加上，调用序列如下图所示：</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-3.png"><img class="aligncenter size-full wp-image-10460" title="加上CreateFile之后的调用序列" src="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-3.png" alt="" width="362" height="317" /></a></p>
<p>在这个函数调用序列当中，存在两次ANSI 到 Unicode 的转换，即图中标记有五角星的两个地方。那么我们来看看两个转换的代码是什么样子的：</p>
<p>LoadLibraryExA:</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-4.png"><img class="aligncenter size-full wp-image-10461" title="LoadLibraryExA函数调用" src="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-4.png" alt="" width="827" height="327" /></a></p>
<p>CreateFileA</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-5.png"><img class="aligncenter size-full wp-image-10462" title="CreateFileA的函数调用" src="http://blog.ftofficer.com/wp-content/uploads/2010/05/ansi-to-unicode-5.png" alt="" width="792" height="375" /></a></p>
<p>注意到其中高亮的部分了么？两个函数在进入之后，都调用了一个公共的函数 _Base8bitStringToStaticUnicodeString，从名字就可以看出来，这段代码负责将输入的ANSI字符串转换为Unicode。那么这个函数做了什么呢？跟进去看看（这个使用了Hex-Rays的反编译功能，看得更清楚一点）：</p>
<p><a href="../wp-content/uploads/2010/06/ansi-to-unicode-6.png"></a><a href="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-6.png"><img class="aligncenter size-full wp-image-10464" title=" Basep8BitStringToStaticUnicodeString函数定义" src="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-6.png" alt="" width="739" height="353" /></a></p>
<p>再仔细看给RtlAntiStringToUnicodeString这个函数传入的参数地址pUnicodeString，来自*MK_FP(__FS__, 24) + 3064，这是Hex-Rays的表示法，其实对应下面一串汇编序列（由于编译器的优化导致的乱序，重点看红线标出来的指令）：</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-7.png"><img class="aligncenter size-full wp-image-10465" title="ansi-to-unicode-7" src="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-7.png" alt="" width="665" height="219" /></a></p>
<p>large fs:18h 这句魔法一样的指令，其实是 <a href="http://en.wikipedia.org/wiki/Win32_Thread_Information_Block" target="_blank">TEB</a> 的地址。这个地址对于同一个线程来说是一样的，因此，如果在一个线程当中调用这个函数，总是会引用到同一个地址，这个地址就是当前线程的TEB当中的一个静态缓冲区（<a href="http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Thread/TEB.html" target="_blank">StaticUnicodeString &amp; StaticUnicodeBuffer</a>）。</p>
<p>很明显，这个静态缓冲区的作用是性能优化，对于ANSI到Unicode的转换，复用一个thread-specific的缓冲区来接受目标Unicode字符串。对于不太长的函数参数字符串，这个优化可以避免一次堆分配的过程，对性能的提升是很明显的。正常情况下这个优化是合理而且有效的。但是在存在Hook的情况下，事情就不是这么简单了。考虑一下上面那张图当中的LoadLibrary的调用序列：</p>
<p><a href="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-8.png"><img class="aligncenter size-full wp-image-10466" title="ansi-to-unicode-8" src="http://blog.ftofficer.com/wp-content/uploads/2010/06/ansi-to-unicode-8.png" alt="" width="700" height="500" /></a></p>
<p>解释一下：</p>
<ol>
<li>在第1次ANSI到Unicode转换的时候， LoadLibraryA将目标Unicode字符串 L”kernel32&#8243; 存入 StaticUnicodeBuffer，然后将这个字符串的首地址传递给LoadLibraryW</li>
<li>LoadLibraryW在调用到LoadLibraryExW之前，先调用到了Hook函数</li>
<li>Hook函数调用到了CreateFileA，传入日志文件名（ANSI）作为函数参数</li>
<li>CreateFileA进行第二次ANSI到Unicode转换，将目标Unicode字符串（日志文件名）存入了StaticUnicodeBuffer，然后将这个字符串首地址传给CreateFileW</li>
<li>CreateFileW返回，</li>
<li>CreateFileA返回，</li>
<li>Hook函数返回，</li>
<li>调用到实际的LoadLibraryExW。此时LoadLibraryExW从参数指针当中取出字符串试图进行加载动态库的操作，但是，它取出的是被在第4步当中覆盖掉的字符串，也就是Unicode版本的日志文件名！试图加载日志文件，自然会收到LoadLibrary失败的返回。‘</li>
<li>LoadLibraryExW返回NULL，LoadLibraryW返回NULL，LoadLibraryA返回NULL。</li>
</ol>
<p>然后，没有检查LoadLibraryA返回值的程序，在调用GetProcAddress的时候，biu~ 掉了。</p>
<p>好了，知道了为什么，怎么解决也就很容易了，在CheckLibrary当中，先备份传入的参数的内容，然后继续走流程，等到需要调用原本函数的时候，将备份的参数传给原本函数。</p>
<p>这就是Hook爱好者们的义务：当你试图改变某个执行流程的时候，请务必清楚地了解关于这个流程的一切。</p>
<p><strong>你也许会喜欢：</strong>
<ul class="similar-posts">
<li><a href="http://blog.ftofficer.com/2007/06/%e6%b5%8f%e8%a7%88%e5%99%a8%e5%92%8cweb%e6%9d%82%e8%b0%88/" rel="bookmark" title="2007年06月26日">浏览器和Web杂谈</a></li>
<li><a href="http://blog.ftofficer.com/2007/09/unicode%e7%9c%9f%e7%9a%84%e6%98%af%e4%b8%80%e4%b8%aa%e9%9d%9e%e5%b8%b8%e6%9c%89%e8%b6%a3%e7%9a%84%e4%b8%9c%e8%a5%bf%e2%80%a6%e2%80%a6/" rel="bookmark" title="2007年09月19日">Unicode真的是一个非常有趣的东西……</a></li>
<li><a href="http://blog.ftofficer.com/2007/10/%e5%8a%a8%e6%89%8b%e5%a2%9e%e5%bc%ba%e4%ba%86%e4%b8%80%e4%b8%8b%e7%ac%94%e8%ae%b0%e6%9c%ac%e4%b8%8a%e7%9a%84%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83/" rel="bookmark" title="2007年10月20日">动手增强了一下笔记本上的开发环境</a></li>
<li><a href="http://blog.ftofficer.com/2009/06/%e6%9c%80%e8%bf%91%e7%9a%84%e7%94%9f%e6%b4%bb/" rel="bookmark" title="2009年06月1日">最近的生活</a></li>
<li><a href="http://blog.ftofficer.com/2008/07/%e6%a0%a1%e5%86%85api/" rel="bookmark" title="2008年07月7日">校内API</a></li>
</ul>
<p><!-- Similar Posts took 46.120 ms --></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ftofficer.com/2010/06/issue-in-kernel32-ansi-to-unicode-translation-and-cache-reentrant/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

