<?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; Zhang Cong</title>
	<atom:link href="http://blog.ftofficer.com/author/admin/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>搭建私有的 python 包发布中心 pypi</title>
		<link>http://blog.ftofficer.com/2010/12/create-private-pypi/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=rss</link>
		<comments>http://blog.ftofficer.com/2010/12/create-private-pypi/#comments</comments>
		<pubDate>Sun, 12 Dec 2010 14:35:49 +0000</pubDate>
		<dc:creator>Zhang Cong</dc:creator>
				<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://blog.ftofficer.com/?p=10418</guid>
		<description><![CDATA[项目组现在使用Python越来越多，大部分老逻辑都已经迁移到了Python上，相当数量的新逻辑都是Python写的。经过之前的一段时间的分享，团队已经开始使用 virtualenv 和 setuptools 来进行Python代码的开发和打包和发布了。 但是现在的问题是，随着项目规模的变大，以及几个子项目的启动，代码复用开始成为一个问题。有很多代码在多个库当中被使用，应该被抽取出来成为单独的模块。但是这些模块和模块的依赖关系会比较复杂，比如一个应用可能需要依赖pypi上开源的库，同时需要依赖一个内部的库，而内部的库又依赖开源的库。如何解决这种情况下的依赖管理和自动包管理呢？ 实际上setuptools 和 easy_install 已经提供了完善的依赖管理，在setup.py当中写上所有依赖的模块名已经在团队内形成了共识，那么内部模块是否可以用同样的方式进行管理呢？ 答案是肯定的。easy_install 本质是 pypi 服务的客户端，它依赖的web服务称为 pypi，或者叫Cheese Shop。是 http://pypi.python.org/simple/ 。这个页面下面就是一系列的 index.html 文件，指向各个版本的包文件。这个index.html本身没有严格的格式规定，只是其中应该包含&#60;a&#62;标签，指向每个版本。easy_install 负责抽取出这些标签，形成一个文件列表，比较版本，下载指定版本或者最近版本，并安装到系统上。 而且，easy_install支持一个命令行参数 &#8211;index-url，或者短参数 -i ，可以指定兼容于 pypi 的 pypi 索引。这个索引只要满足pypi的规定就是可以的，自然，这个是可以自己搭建的。 但是easy_install有一个限制，就是只能指定一个index URL。对于多个index的问题，PEP381明确说了，这是客户端的问题。easy_install选择不解决这个问题，也是一种解决方案吧…… 但是easy_install不解决，我们就要想办法自己解决。去除这个限制有两个方法，一个是让自己的私有pypi在发现私有包里面没有匹配的包名字的时候重定向到pypi.python.org/simple，另一种方法就是使用 easy_install 的替代品 pip。在翻看了众多的部署脚本之后，我们决定，还是使用前面一个策略。 除了常用的easy_install来安装包，pypi还需要一个功能就是支持 distutils 兼容的协议上传包到服务器。对于使用disutils或者setuptools建立的setup.py文件，开发者可以使用 python setup.py register 将项目名注册到 pypi index，也可以通过 python setup.py bdist_egg upload 上传打包好的文件。这个协议很简单，很容易即可实现，只是其中需要的用户管理方面，稍微复杂和体力活一些。 也正是因为简单，搭建私有的pypi服务器的开源程序有很多，PEP 381当中有两个推荐，分别是PloneSoftwareCenter 和 EggBasket。PloneSoftwareCenter是一个恐龙级别的东西，它是一个完整的CMS，pypi只是其中一个小小的功能。为了这样一个简单的功能需要安装一大堆Plone的东西，实在是难以接受，而且它的文档简直是个杯具……，唉。 EggBasket稍好，但是也要安装一堆东西，包括一只小恐龙TurboGears。所幸TurboGears只是一只小恐龙，而且EggBasket本身的文档比较清楚，一步步照着做即可。由于一些安全方面的限制，EggBasket单独的服务器端口在我们的服务器上是不能访问到的，因此我们用apache的mod_proxy做了一个反向代理。 很快，基于EggBasket和apache [...]]]></description>
			<content:encoded><![CDATA[<p>项目组现在使用Python越来越多，大部分老逻辑都已经迁移到了Python上，相当数量的新逻辑都是Python写的。经过之前的一段时间的分享，团队已经开始使用 <a href="http://www.arthurkoziel.com/2008/10/22/working-virtualenv/" target="_blank">virtualenv</a> 和 <a href="http://peak.telecommunity.com/DevCenter/setuptools" target="_blank">setuptools</a> 来进行Python代码的开发和打包和发布了。</p>
<p>但是现在的问题是，随着项目规模的变大，以及几个子项目的启动，代码复用开始成为一个问题。有很多代码在多个库当中被使用，应该被抽取出来成为单独的模块。但是这些模块和模块的依赖关系会比较复杂，比如一个应用可能需要依赖pypi上开源的库，同时需要依赖一个内部的库，而内部的库又依赖开源的库。如何解决这种情况下的依赖管理和自动包管理呢？</p>
<p>实际上setuptools 和 easy_install 已经提供了完善的依赖管理，在setup.py当中写上所有依赖的模块名已经在团队内形成了共识，那么内部模块是否可以用同样的方式进行管理呢？</p>
<p>答案是肯定的。easy_install 本质是 pypi 服务的客户端，它依赖的web服务称为 pypi，或者叫Cheese Shop。是 <a href="http://pypi.python.org/simple/" target="_blank">http://pypi.python.org/simple/</a> 。这个页面下面就是一系列的 index.html 文件，指向各个版本的包文件。这个index.html本身没有严格的格式规定，只是其中应该包含&lt;a&gt;标签，指向每个版本。easy_install 负责抽取出这些标签，形成一个文件列表，比较版本，下载指定版本或者最近版本，并安装到系统上。</p>
<p>而且，easy_install支持一个<a href="http://peak.telecommunity.com/DevCenter/EasyInstall#command-line-options" target="_blank">命令行参数</a> &#8211;index-url，或者短参数 -i ，可以指定兼容于 pypi 的 pypi 索引。这个索引只要满足pypi的规定就是可以的，自然，这个是可以自己搭建的。</p>
<p>但是easy_install有一个限制，就是只能指定一个index URL。对于多个index的问题，PEP381明确<a href="http://www.python.org/dev/peps/pep-0381/#merging-several-indexes" target="_blank">说了</a>，这是客户端的问题。easy_install选择不解决这个问题，也是一种解决方案吧……</p>
<p>但是easy_install不解决，我们就要想办法自己解决。去除这个限制有两个方法，一个是让自己的私有pypi在发现私有包里面没有匹配的包名字的时候重定向到pypi.python.org/simple，另一种方法就是使用 easy_install 的替代品 <a href="http://pip.openplans.org/" target="_blank">pip</a>。在翻看了众多的部署脚本之后，我们决定，还是使用前面一个策略。</p>
<p>除了常用的easy_install来安装包，pypi还需要一个功能就是支持 <a href="http://www.python.org/dev/peps/pep-0243/" target="_blank">distutils 兼容的协议</a>上传包到服务器。对于使用disutils或者setuptools建立的setup.py文件，开发者可以使用 python setup.py register 将项目名注册到 pypi index，也可以通过 python setup.py bdist_egg upload 上传打包好的文件。这个协议很简单，很容易即可实现，只是其中需要的用户管理方面，稍微复杂和体力活一些。</p>
<p>也正是因为简单，搭建私有的pypi服务器的开源程序有很多，PEP 381当中有<a href="http://www.python.org/dev/peps/pep-0381/#extra-package-indexes">两个推荐</a>，分别是PloneSoftwareCenter 和 EggBasket。<a href="http://plone.org/products/plonesoftwarecenter" target="_blank">PloneSoftwareCenter</a>是一个恐龙级别的东西，它是一个完整的CMS，pypi只是其中一个小小的功能。为了这样一个简单的功能需要安装一大堆Plone的东西，实在是难以接受，而且它的文档简直是个杯具……，唉。</p>
<p><a href="http://www.chrisarndt.de/projects/eggbasket/" target="_blank">EggBasket</a>稍好，但是也要安装一堆东西，包括一只小恐龙<a href="http://turbogears.org/" target="_blank">TurboGears</a>。所幸TurboGears只是一只小恐龙，而且EggBasket本身的文档比较清楚，一步步照着做即可。由于一些安全方面的限制，EggBasket单独的服务器端口在我们的服务器上是不能访问到的，因此我们用apache的mod_proxy做了一个反向代理。</p>
<p>很快，基于EggBasket和apache mod_proxy反向代理的私有pypi就搭建起来，问题随即而来：EggBasket不支持我们上面要求的自动重定向。所幸源代码也不多，简单修改了一下之后，做了一个<a href="http://blog.ftofficer.com/pub/patches/python/eggbasket-enable-redirect-404.patch" target="_blank">patch</a>。需要的可以下载下来自己apply。我已经联系了EggBasket的作者，希望能够将这个patch合并进官方代码，但是作者表示，他现在正在休假。【update @2010-12-12 自从这个patch发送过去已经接近半年了，还没有响应，好吧，我放弃了】</p>
<p>经过这些patch，我们的pypi服务器就成功搭建起来了。项目组使用它的方式是：</p>
<p>开发机：<br />
<code>(dev) zhangc@dev-01:pypismpl$ python setup.py register -r http://pypi-server/pypi<br />
We need to know who you are, so please choose either:<br />
 1. use your existing login,<br />
 2. register as a new user,<br />
 3. have the server generate a new password for you (and email it to you), or<br />
 4. quit<br />
Your selection [default 1]:  1<br />
Username: zhangc<br />
Password: ********<br />
Server response (200): OK<br />
I can store your PyPI login so future submissions will be faster.<br />
(the login will be stored in /home/zhangc/.pypirc)<br />
Save your login (y/N)? y<br />
</code></p>
<p>这步操作执行一次即可，如果最后一步选择了save login，则后面不再需要每次都register。</p>
<p>在程序新版本稳定了之后，执行：<br />
<code>(dev) zhangc@dev-01:pypismpl$ python setup.py bdist_egg upload -r http://pypi-server/pypi/upload<br />
</code><br />
即可把新版本的程序打包成egg并且上传到服务器。</p>
<p>这时候如果通过浏览器访问pypi服务器，会发现新版本的pypismpl程序已经在页面上列出了。</p>
<p>然后在生产机上：<br />
<code>(dev) zhangc@production-01:~$ sudo -u appuser -E /usr/app/env/bin/python -i  http://pypi-server/pypi -U pypismpl<br />
</code></p>
<p>新版本的程序就会自动部署了。</p>
<p><strong>你也许会喜欢：</strong>
<ul class="similar-posts">
<li><a href="http://blog.ftofficer.com/2009/12/python-multiprocessing-2-object-sharing-across-process/" rel="bookmark" title="2009年12月7日">Python multiprocessing 使用手记[2] – 跨进程对象共享</a></li>
<li><a href="http://blog.ftofficer.com/2009/12/python-multiprocessing-3-about-queue/" rel="bookmark" title="2009年12月13日">Python multiprocessing 使用手记[3] – 关于Queue</a></li>
<li><a href="http://blog.ftofficer.com/2009/11/using-python-multiprocessing-forword/" rel="bookmark" title="2009年11月9日">Python multiprocessing库使用手记（引子）</a></li>
<li><a href="http://blog.ftofficer.com/2007/06/google-safe-browsing-api-demo/" rel="bookmark" title="2007年06月24日">Google Safe Browsing API Demo</a></li>
<li><a href="http://blog.ftofficer.com/2008/05/%e5%a6%82%e4%bd%95%e4%bf%ae%e6%94%b9%e7%bf%b0%e6%9e%97v3%e7%9a%84%e5%9b%ba%e4%bb%b6/" rel="bookmark" title="2008年05月1日">如何修改翰林V3的固件</a></li>
</ul>
<p><!-- Similar Posts took 9.602 ms --></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ftofficer.com/2010/12/create-private-pypi/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<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 17.070 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>
		<item>
		<title>当心一种利用手机和银行转账汇款的火车票诈骗</title>
		<link>http://blog.ftofficer.com/2010/04/a-tain-ticket-fraud/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=rss</link>
		<comments>http://blog.ftofficer.com/2010/04/a-tain-ticket-fraud/#comments</comments>
		<pubDate>Mon, 26 Apr 2010 18:05:01 +0000</pubDate>
		<dc:creator>Zhang Cong</dc:creator>
				<category><![CDATA[生活]]></category>
		<category><![CDATA[knowledge]]></category>
		<category><![CDATA[life]]></category>

		<guid isPermaLink="false">http://blog.ftofficer.com/?p=10442</guid>
		<description><![CDATA[今天同事去买车票的时候遇到的。虽然最终我们并没有上当，但是还是把整个流程在这里写出来，以便更多的人识别骗局。 首先把这个骗局场景给大家讲述一下，碰到类似的情况，请对号入座，看看自己是不是受害人或者受害人的朋友。为了讲述方便，我们把这个事情当中的角色用三个名字来说明：买票人、黄牛、以及受害人的朋友（简称朋友，是付款人）。其中黄牛就是骗子。在我碰到的这个案例当中，同事的角色是买票人，我的角色是付款人。 这个骗局当中，买票人在黄牛的指引下，引导买票人通过手机和一个朋友联系，并精心设计流程得手。这个过程如下： 黄牛事先知道某车次的票被售罄，然后以此车次的票为诱饵在网上发帖转让；或者笼统的说自己可以搞到票，等待受害者上钩。 买票人需要去购买火车票，但是很不幸，他要买的的火车票没有了； 买票人通过某这方式找到黄牛，并且联系上了他； 黄牛告诉买票人，他可以搞到票，但是由于某种原因（原因五花八门，但通常黄牛声称是内部人士，需要规避风险），他不能进行现金交易，而需要通过下面的步骤来进行： 买票人需要用自己的手机联系一位朋友，并且这样告诉他的朋友：“我在买票，有人能搞到票，但是需要你帮忙把钱汇到某个账户，然后他会给我票。”，并且把下面的步骤向朋友解释； 黄牛用自己的手机联系买票人的朋友，重复说明该流程，并提供一个银行帐户给买票人的朋友； 黄牛将票给买票人，买票人将自己的手机给黄牛； 黄牛使用买票人的手机联系买票人的朋友，这说明第3步已经完成，买票人已经拿到了票，而自己正拿着买票人的手机，朋友需要转账给提供的账户来让买票者赎回手机（在给买票者及其朋友讲述这个流程的时候，骗子强调，由于票已经在给了买票人，只有当他拿到了票款，才能把手机还回去，防止买票人拿回手机之后不付钱）； 朋友将钱汇到黄牛提供的帐号； 黄牛确认钱已经收到之后，将手机还给买票人； 买票人带走车票，回去之后将票款还给朋友。 买票人按要求执行 4.1 当中的操作，给一个朋友打电话并解释； 黄牛执行 4.2 当中的操作，给该朋友打电话并解释，同时提供帐号； 黄牛和买票人执行 4.3 当中的操作，用手机交换票； 黄牛用买票人的手机打电话给他的朋友，要求汇款； 朋友汇款； 黄牛确认款项收到之后，将手机还给买票人； 交易完成 这个过程有什么问题么？看起来无懈可击，通过使用手机和票的抵押，交易双方的利益都得到了保证。而依靠手机号码的识别性，朋友也能够验证买票人的身份。 但是整个流程当中仍然有几个可疑的地方，就是上面的这些红色标记的地方。 首先，买票人和黄牛一定是面对面的，因为两人可以互相交换手机和票，因此，没有理由两人不可以交换人民币。而且，火车票之类的东西并非非常高价，通常数百元，现金并不多。当然，在我同事遇到的这个例子当中，黄牛宣称他和我的同事之间隔着一块玻璃墙，因为他提供的是“内部票”，但是这个借口仍然经不起推敲。隔着一个玻璃墙递送手机，比递送人民币更加引人注目。而且，根据我对火车站流程的了解，他们不存在所谓的“内部票”一说。 第二，如果当我接到电话，是黄牛在说话，但是是我同事的手机号的时候，能够证明黄牛正在使用我同事的手机吗？很多人也许认为是可以的，但是当你知道这个世界上存在一种叫做手机改号软件（提供商）的时候，你就能够知道其实这个什么都不能证明。改号软件利用的是电信系统来电显示的漏洞，通过非法的电话网关接入运营商的网络，并且伪造拨打方的号码。通过此类软件，骗子实际上可以假冒任何人的身份进行通信。作为可能的受害者的朋友，应对此类系统的策略其实很简单，一种方式是要求和朋友讲话（这也是很多警匪片里面侦探确认人质仍然活着的方式）；另一种方式是在收到朋友的手机号打来的电话请求时，不是接听而是回拨。第一种方式在我遇到的这个例子当中已经被骗子废掉了，因为骗子强调，为了保护他的权益，他不能把手机还给买票人，而正如他之前所说，他和买票人之间隔着一堵 玻璃墙，因为他拿“内部票”是需要到售票窗里面去的，因此我也不可能听见同事的声音。但是第二种方式仍然是可用的，因为手机改号软件本身的缺陷，回拨的请求会真的发送到同事的手机上，而不是骗子伪造的通话端。因此，通过接到电话回拨的方式，一定程度上可以完成一次相对可靠的认证，从而避免上当受骗。 你知道了改号软件，那么就很容易想到，在上面的步骤当中，在第四步完成之后，买票人和黄牛分别为买票人的朋友解释了流程之后，很可能的过程是： 骗子借口离开买票人（这个很可能是通过说“我去拿票”）； 骗子使用号码伪造软件，伪造买票人的手机号码向买票人的朋友通话，要求转账； 朋友转账； 骗子消失，朋友和买票人受骗。 随便Google了一下，这种诈骗手段还真是比较新的，大部分都是最近的帖子： 案例1，案例2，案例3，案例4 我和同事很幸运，没有成为那个上当的一对通信者。回顾整个过程，讲讲我的判断过程，也希望能够给其他人防范未知诈骗提供一些帮助。 在同事给我打电话讲述流程的时候，我问了他两个问题：1. 你是否看到这个人了？2. 你是否看到票了。同事对第一个回答是是，第二个却是否。这个是第一个引起我怀疑的地方，甚至，我重新检查了来电号码，仔细辨认了同事的声音，以便确定电话的确是他打来的，同时仔细听了背景声音，确定他的确在火车站。但是我还是建议他先看到票，然后再说交易。无论什么交易，先看到货，再谈交易是基本原则，除非有一个足够强势的第三方担保，例如淘宝。 在同事挂掉电话之后，我正在琢磨整个流程的时候，我接到了骗子的电话。同事之前完全没有告诉我骗子会给我打电话。而骗子在电话接通之后就非常流利的开始讲解整个流程，坦率地讲，这个骗子是一个优秀的社会工程学专家，在整个过程当中他语速一致，条例清楚，伪装也很巧妙，尤其是通过“内部人士”的角色，成功规避了“要求听到同事的声音”这种请求。但是我还是从对话当中发现了他的一些疑点： 此人能够给我打电话，讲述这样一个复杂的流程，却不能接受现金交易，而且借口是为了逃避监管。而实际上，为了逃避监管，现金交易是首选，转帐反而会留下证据（大家不知道发现金的那些单位么？），这是疑点一。 第二，骗子一直在强调，他和同事是“隔着玻璃”的，但是同事说，他们在一起。 骗子坚持要求我使用ATM转帐而不是专业版网银，而且只透漏帐号，拒绝透漏帐户名（ATM转帐不需要户名，户名是在输入帐号之后显示的，但是专业版转帐是需要先提供户名，由系统进行匹配的），目的也许是为了让我能够远离互联网，我于是忽悠他说我已经在ATM机旁边了，对方明显松了一口气，然后转而告诉我，别着急，等他电话。 这些疑点足够我产生怀疑了。接下来就是搜索。使用“火车票”，“转帐”，“手机”等关键字组合搜索之后，很快就发现了大量的此类案例，然后立即拨同事的电话，告知他一切。而这个时候，同事正在和他一起走向某个地方的路上。同事听明白我讲的话之后，挂掉电话。 不知道他和骗子说了什么，骗子马上打来了一个电话，是他的号码，我还没有接，他就挂掉了，也许是做贼心虚了。 公开骗子的电话和帐号：手机 18773861321，帐号 6225 8878 3931 1236。大家可以搜索一下，这个号码在很多网站都留了联系方式。 [...]]]></description>
			<content:encoded><![CDATA[<p>今天同事去买车票的时候遇到的。虽然最终我们并没有上当，但是还是把整个流程在这里写出来，以便更多的人识别骗局。</p>
<p>首先把这个骗局场景给大家讲述一下，碰到类似的情况，请对号入座，看看自己是不是受害人或者受害人的朋友。为了讲述方便，我们把这个事情当中的角色用三个名字来说明：买票人、黄牛、以及受害人的朋友（简称朋友，是付款人）。其中黄牛就是骗子。在我碰到的这个案例当中，同事的角色是买票人，我的角色是付款人。</p>
<p>这个骗局当中，买票人在黄牛的指引下，引导买票人通过手机和一个朋友联系，并精心设计流程得手。这个过程如下：</p>
<ol>
<li>黄牛事先知道某车次的票被售罄，然后以此车次的票为诱饵在网上发帖转让；或者笼统的说自己可以搞到票，等待受害者上钩。</li>
<li>买票人需要去购买火车票，但是很不幸，他要买的的火车票没有了；</li>
<li>买票人通过某这方式找到黄牛，并且联系上了他；</li>
<li>黄牛告诉买票人，他可以搞到票，但是由于某种原因（原因五花八门，但通常黄牛声称是内部人士，需要规避风险），他<span style="color: #ff0000;"><strong>不能进行现金交易</strong></span>，而需要通过下面的步骤来进行：
<ol>
<li>买票人需要用自己的手机联系一位朋友，并且这样告诉他的朋友：“我在买票，有人能搞到票，但是需要你帮忙把钱汇到某个账户，然后他会给我票。”，并且把下面的步骤向朋友解释；</li>
<li>黄牛用自己的手机联系买票人的朋友，重复说明该流程，并提供一个银行帐户给买票人的朋友；</li>
<li>黄牛将票给买票人，买票人将自己的手机给黄牛；</li>
<li>黄牛使用买票人的手机联系买票人的朋友，这说明第3步已经完成，<span style="color: #ff0000;"><strong>买票人已经拿到了票，</strong><strong>而自己正拿着买票人的手机</strong></span>，朋友需要转账给提供的账户来让买票者赎回手机（在给买票者及其朋友讲述这个流程的时候，骗子强调，由于票已经在给了买票人，<strong><span style="color: #ff0000;">只有当他拿到了票款，才能把</span></strong><span style="color: #ff0000;"><strong><span style="color: #ff0000;">手机还回去</span></strong></span>，防止买票人拿回手机之后不付钱）；</li>
<li>朋友将钱汇到黄牛提供的帐号；</li>
<li>黄牛确认钱已经收到之后，将手机还给买票人；</li>
<li>买票人带走车票，回去之后将票款还给朋友。</li>
</ol>
</li>
<li>买票人按要求执行 4.1 当中的操作，给一个朋友打电话并解释；</li>
<li>黄牛执行 4.2 当中的操作，给该朋友打电话并解释，同时提供帐号；</li>
<li>黄牛和买票人执行 4.3 当中的操作，用手机交换票；</li>
<li>黄牛用买票人的手机打电话给他的朋友，要求汇款；</li>
<li>朋友汇款；</li>
<li><span style="color: #000000;">黄牛确认款项收到之后</span>，将手机还给买票人；</li>
<li>交易完成</li>
</ol>
<p>这个过程有什么问题么？看起来无懈可击，通过使用手机和票的抵押，交易双方的利益都得到了保证。而依靠手机号码的识别性，朋友也能够验证买票人的身份。</p>
<p>但是整个流程当中仍然有几个可疑的地方，就是上面的这些红色标记的地方。</p>
<p>首先，买票人和黄牛一定是面对面的，因为两人可以互相交换手机和票，因此，没有理由两人不可以交换人民币。而且，火车票之类的东西并非非常高价，通常数百元，现金并不多。当然，在我同事遇到的这个例子当中，黄牛宣称他和我的同事之间隔着一块玻璃墙，因为他提供的是“内部票”，但是这个借口仍然经不起推敲。隔着一个玻璃墙递送手机，比递送人民币更加引人注目。而且，根据我对火车站流程的了解，他们不存在所谓的“内部票”一说。</p>
<p>第二，如果当我接到电话，是黄牛在说话，但是是我同事的手机号的时候，能够证明黄牛正在使用我同事的手机吗？很多人也许认为是可以的，但是当你知道这个世界上存在一种叫做<a href="http://www.google.com/search?hl=en&amp;q=%E6%89%8B%E6%9C%BA%E6%94%B9%E5%8F%B7%E8%BD%AF%E4%BB%B6&amp;sourceid=navclient-ff&amp;rlz=1B3GGGL_zh-CNCN318CN274&amp;ie=UTF-8">手机改号软件</a>（<a href="http://www.166a.cn/" target="_blank">提供商</a>）的时候，你就能够知道其实这个什么都不能证明。改号软件利用的是电信系统来电显示的漏洞，通过非法的电话网关接入运营商的网络，并且伪造拨打方的号码。通过此类软件，骗子实际上可以假冒任何人的身份进行通信。作为可能的受害者的朋友，应对此类系统的策略其实很简单，一种方式是要求和朋友讲话（这也是很多警匪片里面侦探确认人质仍然活着的方式）；另一种方式是在收到朋友的手机号打来的电话请求时，<strong>不是接听而是回拨</strong>。第一种方式在我遇到的这个例子当中已经被骗子废掉了，因为骗子强调，为了保护他的权益，他不能把手机还给买票人，而正如他之前所说，他和买票人之间隔着一堵 玻璃墙，因为他拿“内部票”是需要到售票窗里面去的，因此我也不可能听见同事的声音。但是第二种方式仍然是可用的，因为手机改号软件本身的缺陷，回拨的请求会真的发送到同事的手机上，而不是骗子伪造的通话端。因此，<strong>通过接到电话回拨的方式，一定程度上可以完成一次相对可靠的认证</strong>，从而避免上当受骗。</p>
<p>你知道了改号软件，那么就很容易想到，在上面的步骤当中，在第四步完成之后，买票人和黄牛分别为买票人的朋友解释了流程之后，很可能的过程是：</p>
<ol>
<li>骗子借口离开买票人（这个很可能是通过说“我去拿票”）；</li>
<li>骗子使用号码伪造软件，伪造买票人的手机号码向买票人的朋友通话，要求转账；</li>
<li>朋友转账；</li>
<li>骗子消失，朋友和买票人受骗。</li>
</ol>
<p>随便Google了一下，这种诈骗手段还真是比较新的，大部分都是最近的帖子：<a href="http://news.huochepiao.com/2010-2/2010213125543.htm" target="_blank"><br />
案例1</a>，<a href="http://bbs.huochepiao.com/html/230/284678.htm" target="_blank">案例2</a>，<a href="http://it.sohu.com/20100423/n271694823.shtml" target="_blank">案例3</a>，<a href="http://apps.hi.baidu.com/share/detail/1067374" target="_blank">案例4</a></p>
<p>我和同事很幸运，没有成为那个上当的一对通信者。回顾整个过程，讲讲我的判断过程，也希望能够给其他人防范未知诈骗提供一些帮助。</p>
<p>在同事给我打电话讲述流程的时候，我问了他两个问题：1. 你是否看到这个人了？2. 你是否看到票了。同事对第一个回答是是，第二个却是否。这个是第一个引起我怀疑的地方，甚至，我重新检查了来电号码，仔细辨认了同事的声音，以便确定电话的确是他打来的，同时仔细听了背景声音，确定他的确在火车站。但是我还是建议他先看到票，然后再说交易。<strong>无论什么交易，先看到货，再谈交易是基本原则</strong>，除非有一个足够强势的第三方担保，例如淘宝。</p>
<p>在同事挂掉电话之后，我正在琢磨整个流程的时候，我接到了骗子的电话。同事之前完全没有告诉我骗子会给我打电话。而骗子在电话接通之后就非常流利的开始讲解整个流程，坦率地讲，这个骗子是一个优秀的<a href="http://en.wikipedia.org/wiki/Social_engineering_%28security%29" target="_blank">社会工程学</a>专家，在整个过程当中他语速一致，条例清楚，伪装也很巧妙，尤其是通过“内部人士”的角色，成功规避了“要求听到同事的声音”这种请求。但是我还是从对话当中发现了他的一些疑点：</p>
<ol>
<li>此人能够给我打电话，讲述这样一个复杂的流程，却不能接受现金交易，而且借口是为了逃避监管。而实际上，<strong>为了逃避监管，现金交易是首选</strong>，转帐反而会留下证据（大家不知道发现金的那些单位么？），这是疑点一。</li>
<li>第二，骗子一直在强调，他和同事是“隔着玻璃”的，但是同事说，他们在一起。</li>
<li>骗子坚持要求我使用ATM转帐而不是专业版网银，而且只透漏帐号，拒绝透漏帐户名（ATM转帐不需要户名，户名是在输入帐号之后显示的，但是专业版转帐是需要先提供户名，由系统进行匹配的），目的也许是为了让我能够远离互联网，我于是忽悠他说我已经在ATM机旁边了，对方明显松了一口气，然后转而告诉我，别着急，等他电话。</li>
</ol>
<p>这些疑点足够我产生怀疑了。接下来就是搜索。使用“火车票”，“转帐”，“手机”等关键字组合搜索之后，很快就发现了大量的此类案例，然后立即拨同事的电话，告知他一切。而这个时候，同事正在和他一起走向某个地方的路上。同事听明白我讲的话之后，挂掉电话。</p>
<p>不知道他和骗子说了什么，骗子马上打来了一个电话，是他的号码，我还没有接，他就挂掉了，也许是做贼心虚了。</p>
<p>公开骗子的电话和帐号：手机 18773861321，帐号 6225 8878 3931 1236。大家可以搜索一下，这个号码在很多网站都留了联系方式。</p>
<p>PS: 推荐《<a href="http://book.douban.com/subject/3068994/" target="_blank">欺骗的艺术</a>》（又译《入侵的艺术》）一书，我所学习的识别骗子的技巧，相当一部分来自此书。【<a href="http://www.shucang.com/book.php?sub=view_doc&amp;viewkey=cfe3bbacd3dc594cef29&amp;page=1&amp;viewtype=&amp;category=mr" target="_blank">下载</a>】</p>
<p><strong>你也许会喜欢：</strong>
<ul class="similar-posts">
<li><a href="http://blog.ftofficer.com/2007/10/gphone-is-coming/" rel="bookmark" title="2007年10月10日">GPhone is coming</a></li>
<li><a href="http://blog.ftofficer.com/2008/09/%e7%94%a8google-cn%e5%81%9aproxy%e8%ae%bf%e9%97%aegoogle%e7%9a%84%e6%9c%8d%e5%8a%a1/" rel="bookmark" title="2008年09月17日">用google.cn做proxy访问Google的服务</a></li>
<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/12/2008%e5%b9%b4%e6%ac%a7%e6%b4%b2%e6%9d%af%e6%97%a5%e5%8e%86/" rel="bookmark" title="2007年12月3日">2008年欧洲杯日历</a></li>
<li><a href="http://blog.ftofficer.com/2008/11/%e7%be%8e%e5%9b%bd%e4%b9%8b%e8%a1%8c%e5%b0%8f%e8%ae%b0%ef%bc%882%ef%bc%89/" rel="bookmark" title="2008年11月4日">美国之行小记（2）</a></li>
</ul>
<p><!-- Similar Posts took 14.824 ms --></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ftofficer.com/2010/04/a-tain-ticket-fraud/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>CALL指令有多少种写法</title>
		<link>http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=rss</link>
		<comments>http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/#comments</comments>
		<pubDate>Sat, 03 Apr 2010 19:02:00 +0000</pubDate>
		<dc:creator>Zhang Cong</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[assembly]]></category>
		<category><![CDATA[fun]]></category>

		<guid isPermaLink="false">http://blog.ftofficer.com/?p=10407</guid>
		<description><![CDATA[最近有一个需求，给你个地址，看看这个地址前面是不是一个CALL指令（请同学们自行联想该需求的来源）。作为团队的救火队员+炮灰，这个简单的事情自然落在了我的头上。 这个事情很简单，作为一个善于站在别人肩膀上的程序员我们可以考虑使用 libdisasm；如果要考虑x64，就试试udis86；如果需要用Python，就有Python包装好的 pydasm。不过这两个400KB+的库，显然不值得为了一个CALL指令导入到编译出来大小仅仅100K不到的项目代码里面。 那么就自己抽一个CALL指令解码逻辑出来好了。这个逻辑的复杂性在于，你无法知道前面一个CALL指令有多长。因此，首先需要枚举出所有的CALL指令格式。 Intel有公开的指令集格式文档，你需要的是第二卷的上半部分，指令集从A到M。这篇文档的难度超出一般人想象，里面有众多晦涩的标识、与硬件紧密相关的介绍，拿到这后，即使直接翻到目录的CALL 指令一节，也不见得能够弄清楚。不相信？我们就翻到那里看看： 虽然很明确的列出，第一列是指令的二进制形式，第二列是指令的汇编形式，但是面对着 E8 cw, FF/2这样的标识，一样不知道究竟对应的二进制格式是什么样的。 那好，我们就从理解这些标识开始。文档向前翻，有一个专门的节(3.1.1 Instruction Format）讲述这些标识的含义。这里抽出其中两个用得着的翻译一下： 表格中的“Opcode”列列出了所有的所有可能的指令对应的二进制格式。有可能的话，指令代码使用十六进制显示它们在内存当中的字节。除了这些16进制代码之外的部分使用下面的标记： cb, cw, cd, cp, co, ct — opcode后面跟着的一个1字节(cb)，2字节(cw)，4字节(cd)，6字节 (cp)，8字节(co) 或者 10字节(ct) 的值。这个值用来表示代码偏移地址，有可能的话还包括代码段寄存器的值。 /digit — digit为0到7之间的数字，表示指令的 ModR/M byte 只使用 r/m字段作为操作数，而其reg字段作为opcode的一部分，使用digit指定的数字。 红字部分不知道什么含义？没关系，我们先不看它。对于cb/cw之类的，基本上能够简单看明白其中的一些指令含义了： E8 cw 的含义是：字节 0xE8 后面跟着一个2字节操作数表示要跳转到的地址与当前地址的偏移量。 E8 cd 的含义是：字节 0xE8 后面跟着一个4字节的操作数表示要跳转的地址与当前地址的偏移量。 9A cd 的含义是：字节 0x9A 后面跟着一个6字节的操作数表示要跳转的地址和代码段寄存器的值。 那么，同样的0xE8开头的指令，CPU如何区分后面的操作数是2字节还是4字节？答案是和CPU的模式有关，在实模式下，0xE8接受2字节操作数，而32位保护模式下接受4个字节，64位保护模式下同样接受4字节，同时需要对该操作数进行带符号扩展。 因此，CALL指令的前两种格式是：E8 xx xx xx [...]]]></description>
			<content:encoded><![CDATA[<p>最近有一个需求，给你个地址，看看这个地址前面是不是一个CALL指令（请同学们自行联想该需求的来源）。作为团队的救火队员+炮灰，这个简单的事情自然落在了我的头上。</p>
<p>这个事情很简单，作为一个善于站在别人肩膀上的程序员我们可以考虑使用 <a href="http://bastard.sourceforge.net/libdisasm.html" target="_blank">libdisasm</a>；如果要考虑x64，就试试<a href="http://udis86.sourceforge.net/" target="_blank">udis86</a>；如果需要用Python，就有Python包装好的 <a href="http://dkbza.org/pydasm.html" target="_blank">pydasm</a>。不过这两个400KB+的库，显然不值得为了一个CALL指令导入到编译出来大小仅仅100K不到的项目代码里面。</p>
<p>那么就自己抽一个CALL指令解码逻辑出来好了。这个逻辑的复杂性在于，你无法知道前面一个CALL指令有多长。因此，首先需要枚举出所有的CALL指令格式。</p>
<p>Intel有公开的<a href="http://www.intel.com/products/processor/manuals/">指令集格式文档</a>，你需要的是<a href="http://www.intel.com/Assets/PDF/manual/253666.pdf" target="_blank">第二卷的上半部分</a>，指令集从A到M。这篇文档的难度超出一般人想象，里面有众多晦涩的标识、与硬件紧密相关的介绍，拿到这后，即使直接翻到目录的CALL 指令一节，也不见得能够弄清楚。不相信？我们就翻到那里看看：</p>
<div id="attachment_10421" class="wp-caption aligncenter" style="width: 529px"><a href="http://blog.ftofficer.com/wp-content/uploads/2010/04/CALL-Procedure-instruction.png"><img class="size-full wp-image-10421 " title="CALL指令格式一览表" src="http://blog.ftofficer.com/wp-content/uploads/2010/04/CALL-Procedure-instruction.png" alt="CALL指令格式一览表" width="519" height="317" /></a><p class="wp-caption-text">CALL指令格式一览表</p></div>
<p>虽然很明确的列出，第一列是指令的二进制形式，第二列是指令的汇编形式，但是面对着 E8 cw, FF/2这样的标识，一样不知道究竟对应的二进制格式是什么样的。</p>
<p>那好，我们就从理解这些标识开始。文档向前翻，有一个专门的节(3.1.1 Instruction Format）讲述这些标识的含义。这里抽出其中两个用得着的翻译一下：</p>
<blockquote><p>表格中的“Opcode”列列出了所有的所有可能的指令对应的二进制格式。有可能的话，指令代码使用十六进制显示它们在内存当中的字节。除了这些16进制代码之外的部分使用下面的标记：</p>
<p><strong>cb, cw, cd, cp, co, ct</strong> — opcode后面跟着的一个1字节(cb)，2字节(cw)，4字节(cd)，6字节  (cp)，8字节(co) 或者 10字节(ct) 的值。这个值用来表示代码偏移地址，有可能的话还包括代码段寄存器的值。</p>
<p><strong>/digit</strong> — digit为0到7之间的数字，表示指令的 <span style="color: #ff0000;"><strong>ModR/M byte</strong> </span>只使用 <span style="color: #ff0000;"><strong>r/m字段</strong></span>作为操作数，而其<span style="color: #ff0000;"><strong>reg字段</strong></span>作为opcode的一部分，使用digit指定的数字。</p></blockquote>
<p>红字部分不知道什么含义？没关系，我们先不看它。对于cb/cw之类的，基本上能够简单看明白其中的一些指令含义了：</p>
<p>E8 cw 的含义是：字节 0xE8 后面跟着一个2字节操作数表示要跳转到的地址与当前地址的偏移量。<br />
E8 cd 的含义是：字节 0xE8 后面跟着一个4字节的操作数表示要跳转的地址与当前地址的偏移量。<br />
9A cd 的含义是：字节 0x9A 后面跟着一个6字节的操作数表示要跳转的地址和代码段寄存器的值。</p>
<p>那么，同样的0xE8开头的指令，CPU如何区分后面的操作数是2字节还是4字节？答案是和CPU的模式有关，在实模式下，0xE8接受2字节操作数，而32位保护模式下接受4个字节，64位保护模式下同样接受4字节，同时需要对该操作数进行带符号扩展。</p>
<p>因此，CALL指令的前两种格式是：E8 xx xx xx xx，和 9A xx xx xx xx xx xx。一个是5字节长，一个是7字节长。其实E8 那种，就是我们在汇编指令里面写 CALL lable之后产生的，最常见的CALL指令。</p>
<p>然后是下面的FF /2。这个是0xFF字节后面跟上一个blablabla的东西。这个blablabla的东西是什么呢？要解释这个，首先需要知道红字标出来的部分，即ModR/M是什么东西。</p>
<p>这个要先回到最基本的一个问题：IA32的指令格式。</p>
<div id="attachment_10408" class="wp-caption aligncenter" style="width: 398px"><a href="http://blog.ftofficer.com/wp-content/uploads/2010/03/IA32-64-instruction-format.png" target="_blank"><img class="size-full wp-image-10408  " title="IA32，64指令格式" src="http://blog.ftofficer.com/wp-content/uploads/2010/03/IA32-64-instruction-format.png" alt="IA32，64指令格式" width="388" height="152" /></a><p class="wp-caption-text">IA-32，Intel 64指令格式</p></div>
<p>其中每个部分是什么含义呢？</p>
<p>首先是指令前缀。有印象的应该记得当年学习微机原理的时候提到过得循环前缀 repnz/repne，这个前缀就是被编码在指令的前面部分的。每个前缀最多一个字节，一条指令最多4个前缀。</p>
<p>然后是指令代码（opcode），这部分标识了指令是什么。这个是指令当中唯一必需的部分。前面例子当中的 0xE8，0xFF都是opcode。</p>
<p>再后面就是我们要重点关心的 ModR/M字段了，还有和它密切相关的SIB字节。手册2.1.3当中有对于它们的详细描述。</p>
<blockquote><p>许多指令需要引用到一个在内存当中的值作为操作数，这种指令需要一个称为<strong>寻址模式标识字节</strong>（addressing-form specifier byte），或者叫做ModR/M字节紧跟在主opcode后面。ModR/M字节包含下面三个部分的信息：</p>
<ul>
<li>mod（模式）域，连同r/m（寄存器/内存）域共同构成了32个可能的值：8个寄存器和24个寻址模式。</li>
<li>reg/opcode（寄存器/操作数）域指定了8个寄存器或者额外的3个字节的opcode。究竟这三个字节用来做什么由主opcode指定。</li>
<li>r/m（寄存器/内存）域可以指定一个寄存器作为操作数，或者可以和mod域联合用来指定寻址模式。有时候，它和mod域一起用来为某些指令指定额外的信息。</li>
</ul>
</blockquote>
<p>这一段有些晦涩。其意思解释一下是这样的：一个指令往往需要引用一个在内存当中的值，典型的就是如mov：</p>
<p>MOV eax, dword ptr<strong> [123456]<br />
</strong>MOV eax, dword ptr <strong>[esi]</strong></p>
<p>这其中的 123456 或者 esi 就是 MOV 指令引用的内存地址，而MOV关心的是这个地址当中的内容。这个时候，需要某种方式来为指令指定这个操作数的类型：是一个立即数表示的地址，还是一个存放在寄存器当中的地址，或者，就是寄存器本身。</p>
<p>这个用来区分操作数类型的指令字节就是 ModR/M，确切的说是其中的5个位，即mod和r/m域。剩下的三个位，可能用来做额外的指令字节。因为，IA32的指令个数已经远超过一个字节所能表示的256个了。因此，有的指令就要复用第一个字节，然后依据ModR/M当中的reg/opcode域进行区分。</p>
<p>现在回头看前面的红字标识的部分，能不能理解 /digit 这种表示法了？</p>
<p>对于SIB的介绍，我们先忽略，看看对于CALL指令的枚举我们已经能做什么了。</p>
<p>CALL指令的表示法：FF /2，是 0xFF 后面跟着一个 /digit 表示的东西。就是说，0xFF后面需要跟一个 ModR/M 字节，ModR/M字节使用 reg/opcode 域 = 2 。那么，reg/opcode = 2 的字节有32个，正如ModR/M的解释，这32个值代表了32种不同的寻址方式。是哪32种呢？手册上面有张表：</p>
<div id="attachment_10425" class="wp-caption aligncenter" style="width: 601px"><a href="http://blog.ftofficer.com/wp-content/uploads/2010/04/ModRM-2.png"><img class="size-full wp-image-10425" title="32字节寻址模式下的ModR/M字节" src="http://blog.ftofficer.com/wp-content/uploads/2010/04/ModRM-2.png" alt="32字节寻址模式下的ModR/M字节" width="591" height="627" /></a><p class="wp-caption-text">32字节寻址模式下的ModR/M字节</p></div>
<p>非常复杂的一张表。现在就看看这张表怎么读。</p>
<p>首先是列的定义。由于 reg/opcode 域可以用来表示opcode，也可以用来表示reg，因此同一个值在不同的指令当中可能代表不同的含义。在表当中，就表现为每一列的表头都有很多个不同的表示。我们需要关心的就是 opcode 这一个。注意看我用红圈圈出来的部分，这一列就是 opcode=2 的一列。而我们需要的 CALL 指令，也就是在这一列当中，0xFF后面需要跟着的内容。</p>
<p>行的定义就是不同的寻址模式。正如手册所说，mod + R/M域，共5个字节，定义了32种寻址模式。0&#215;10 &#8211; 0&#215;17 对应于寄存器寻址。例如指令 CALL dword ptr [eax] ：[eax]寻址对应的是 0&#215;10，因此，该指令对应的二进制就是 FF 10。同理， CALL dword ptr [ebx] 是 FF 13，CALL dword ptr [esi] 是 FF 16，这些指令都是2个字节。有人也许问 CALL word ptr [eax] 是什么？抱歉，这不是一个合法的32位指令。</p>
<p>0&#215;50-0&#215;57部分需要带一个 disp8，即 8bit 立即数，也就是一个字节。这个是基地址+8位偏移量的寻址模式。例如 CALL dword ptr [eax+10] 就是 FF 50 10 。注意虽然表当中写的是 [eax] + disp8 这种形式，但是并不表示是取得 eax 指向的地址当中的值再加上 disp8，而是在eax上加上disp8再进行寻址。因此写成 [eax+disp8] 更不容易引起误解。后面的disp32也是一样的。这个类型指令是3个字节。</p>
<p>0&#215;90 &#8211; 0&#215;97部分需要带 disp32，即4字节立即数。这个是基地址+32位偏移量。例如 CALL dword ptr [eax+12345] 就是 FF 90 00 01 23 45。有趣的是， CALL dword ptr [eax+10] 也可以写成 FF 90 00 00 00 10。至于汇编成哪个二进制形式，这是汇编器的选
