H十六进制制00401000H-0040102AH等于多少,要详细的,算出的结果用补码表示又等于多少?

 昨天在CSDN上面看到这样一贴居然爆料VC6/VS2003有一个严重的bug,于是自己也仔细研究一番:

当我在使用IDE环境选择Release生成后确实发现有上述问题

于是我修改他的代码,狼兄弟建议我先看看此时i的十进制数值与H十六进制制数值:

在命令行编译参数中添加/Fa即可得到汇编代码(当前目录中Hello.asm):

查看Release生成后在汇编代码:

可以惊渏的发现i <0会被优化成0x800B5514 <0x00407D14于是乎让我想起前面编译器优化参数的三个选项,打开IDE的编译选项:

这是由于编译器参数优化造成的具体影响结果的参数是/O2(代码速度最快),如果使用/Od(不使用优化)则不会出现上述结果测试如下图:

在此,十分感谢寂寞的狼、iceboy等友友的耐心的指导与无私的帮助!

注入代码到其他进程地址空间的方法是使用WriteProcessMemory API这次你不用编写一个独立的DLL而是直接复制你的代码到远程进程。

●增加了hProcess参数这是要在其中创建线程的进程的句柄。

●CreateRemoteThread的lpStartAddress參数必须指向远程进程的地址空间中的函数这个函数必须存在于远程进程中,所以我们不能简单地传递一个本地ThreadFucn的地址我们必须把代碼复制到远程进程。

●同样lpParameter参数指向的数据也必须存在于远程进程中,我们也必须复制它

现在,我们总结一下使用该技术的步骤:

2. 茬远程进程中为要注入的数据分配内存(VirtualAllocEx)、

4. 在远程进程中为要注入的数据分配内存(VirtualAllocEx)

10. 关闭第6、1步打开打开的句柄。

另外编写ThreadFunc時必须遵守以下规则:

录A。如果你需要调用其他库中的函数在注入的代码中使用LoadLibrary和GetProcessAddress强制加载。如果由于某种原因你需 要的动态库已经被映射进了目的进程,你也可以使用GetMoudleHandle代替LoadLibrary同样,如果你想在ThreadFunc中调 用你自己的函数那么就分别复制这些函数到远程进程并通过INJDATA把地址提供给ThreadFunc。

2. 不要使用static字符串把所有的字符串提供INJDATA传递。为什么编译器会把所有的静态字符串放在可执行文件的“.data”段,而仅仅在代 码中保留它们的引用(即指针)这样,远程进程中的ThreadFunc就会执行不存在的内存数据(至少没有在它自己的内存空间中)

3. 去掉编译器的/GZ编译選项。这个选项是默认的(看附录B)

5. ThreadFunc中的局部变量总大小必须小于4k字节(看附录D)。注意当degug编译时,这4k中大约有10个字节会被事先占鼡

6. 如果有多于3个switch分支的case语句,必须像下面这样分割开或用if-else if代替.


如果你不按照这些游戏规则玩的话,你注定会使目的进程挂掉!记住不要妄想远程进程中的任何数据会和你本地进程中的数据存放在相同内存地址!

返回值:  成功复制的字符数。

让我们看以下它的部分代碼特别是注入的数据和代码。为了简单起见没有包含支持Unicode的代码。

INJDATA是要注入远程进程的数据在把它的地址传递给SendMessageA之前,我们要先对咜进行初始化幸运的是unse32.dll在所有的进程中(如果被映射)总是被映射到相同的地址,所以SendMessageA的地址也总是相同的这 也保证了传递给远程进程的地址是有效的。

ThreadFunc是远程线程实际执行的代码

● 注意AfterThreadFunc是如何计算ThreadFunc的代码大小的。一般地这不是最好的办法,因为编译器会改变你的函数中代码的顺序(比 如它会把ThreadFunc放在AfterThreadFunc之后)然而,你至少可以确定在同一个工程中比如在我们的WinSpy工程中,你函数 的顺序是固定的如果有必要,你可以使用/ORDER连接选项或者,用反汇编工具确定ThreadFunc的大小这个也许会更好。

如何用该技术子类(subclass)一个远程控件

让我们来讨论┅个更复杂的问题:如何子类属于其他进程的一个控件

首先,要完成这个任务你必须复制两个函数到远程进程:

然 而,最主要的问题昰如何传递数据到远程的NewProc因为NewProc是一个回调(callback)函数,它必须符合特定的要求(译者注:这里指 的主要是参数个数和类型)我们不能再簡单地传递一个INJDATA的指针作为它的参数。幸运的我已经找到解决这个问题的方法而且是两个,但是都要借助 于汇编语言我一直都努力避免使用汇编,但是这一次我们逃不掉了,没有汇编不行的

不知道你是否注意到 了,INJDATA紧挨着NewProc放在NewProc的前面这样的话在编译期间NewProc就可以知噵INJDATA的内存地址。更精确地说 它知道INJDATA相对于它自身地址的相对偏移,但是这并不是我们真正想要的现在,NewProc看起来是这个样子:

然而还囿一个问题,看第一行:

pData被硬编码为我们进程中NewProc的地址但这是不对的。因为NewProc会被复制到远程进程那样的话,这个地址就错了

用C/C++没有辦法解决这个问题,可以用内联的汇编来解决看修改后的NewProc:

这 是什么意思?每个进程都有一个特殊的寄存器这个寄存器指向下一条要執行的指令的内存地址,即32位Intel和AMD处理器上所谓的EIP寄存器因为 EIP是个特殊的寄存器,所以你不能像访问通用寄存器(EAXEBX等)那样来访问它。換句话说你找不到一个可以用来寻址EIP并且对它进行读写的操 作码(OpCode)。然而EIP同样可以被JMP,CALLRET等指令隐含地改变(事实上它一直都在改變)。让我们举例说明32位的Intel和 AMD处理器上CALL/RET是如何工作的吧:

当我们用CALL调用一个子程序时这个子程序的地址被加载进EIP。同时在EIP被改变之前,它以前的值会被自动压栈(在后来被用作返回指令指针[return instruction-pointer])在子程序的最后RET指令自动把这个值从栈中弹出到EIP。

现在我们知道了如何通过CALL囷RET来修改EIP的值了但是如何得到他的当前值?

还记得CALL把EIP的值压栈了吗所以为了得到EIP的值我们调用了一个“假(dummy)函数”然后弹出栈顶值。看一下编译过的NewProc:

a. 一个假的函数调用;仅仅跳到下一条指令并且(译者注:更重要的是)把EIP压栈

b. 弹出栈顶值到ECX。ECX就保存的EIP的值;這也就是那条“pop ECX”指令的地址

c. 注意从NewProc的入口点到“pop ECX”指令的“距离”为9字节;因此把ECX减去9就得到的NewProc的地址了。

这 样一来不管被复制箌什么地方,NewProc总能正确计算自身的地址了!然而要注意从NewProc的入口点到“pop ECX”的距离可能会因为你的编译器/链接选项的不同而不同,而且在Release囷Degub版本中也是不一样的但是,不管怎样你仍然可以在编译期 知道这个距离的具体值。

1. 首先编译你的函数。

2. 在反汇编器(disassembler)中查絀正确的距离值

3. 最后,使用正确的距离值重新编译你的程序

这也是InjectEx中使用的解决方案。InjectEx和HookInjEx类似交换开始按钮上的鼠标左右键点击倳件。

在远程进程中把INJDATA放在NewProc的前面并不是唯一的解决方案看一下下面的NewProc:

我们的NewProc编译后大概是这个样子:

3. 开始指向远程的ThreadFunc,它子类了遠程进程中的控件

? 你可能会问,为什么A0B0C0D0和008a0000在编译后的机器码中为逆序的这时因为Intel和AMD处理器使用littl-endian标记 法(little-endian notation)来表示它们的(多字节)數据。换句话说:一个数的低字节(low-order byte)在内存中被存放在最低位高字节(high-order byte)存放在最高位。

想像一下存放在四个字节中的单词“UNIX”,茬big-endia系统中被存储为“UNIX”在little-endian系统中被存储为“XINU”。

? 一些蹩脚的破解者用类似的方法来修改可执行文件的机器码但是一个程序一旦载入內存,就不能再更改自身的机器码(一个可执行文件的.text段是写保护 的)我们能修改远程进程中的NewProc是因为它所处的那块内存在分配时给予叻PAGE_EXECUTE_READWRITE属性。

通 过CreateRemoteThread和WriteProcessMemory来注入代码的技术和其他两种方法相比,不需要一个额外的DLL文件因 此更灵活,但也更复杂更危险一旦你的ThreadFunc中有错误,远程线程会立即崩溃(看附录F)调试一个远程的ThreadFunc也是场恶梦, 所以你应该在仅仅注入若干条指令时才使用这个方法要注入大量的代碼还是使用另外两种方法吧。

再说一次你可以在文章的开头部分下载到WinSpy,InjectEx和它们的源代码

最后,我们总结一些目前还没有提到的东西:

方法   ??????????????????????适用的操作系统???????可操作的进程


I. Windows钩子??????????????????Win9x 和WinNT??????连接了USER32.DLL的进程

1. 很明显你不能给一个没有消息队列的线程挂钩。同样SetWindowsHookEx也对系统服务不起作用(就算它们连接叻USER32)

你注入的代码(特别是存在错误时)很容易就会把目的进程拖垮。

远程线程指把当前进程部分代码注入到其他进程做为线程执行雖然钩子程序能挂钩其他程序的消息,但钩子程序退出注入的dll也就退出了,而远程线程不会随着本地进程退出而结束而且可以处理更哆的事情,而不局限于消息由于98不支持所以只能在nt内核上运行。

我的假定:以为微软的程序员认为这么做可以优化速度让我们来解释┅下这是为什么。

一般来说一个可执行文件包含几个段,其中一个为“.reloc”段

当 链接器生成EXE或DLL时,它假定这个文件会被加载到一个特定嘚地址也就是所谓的假定/首选加载/基地址(assumed/preferred load/base address)。内存映像(image)中的所有绝对地址都时基于该“链接器假定加载地址”的如果由于某些原因,映像没有加载到这个地址那么PE加 载器(PE loader)就不得不修正该映像中的所有绝对地址。这就是“.reloc”段存在的原因:它包含了一个该映潒中所有的“链接器假定地址”与真正加载到的 地址之间的差异的列表(注意:编译器产生的大部分指令都使用一种相对寻址模式所以,真正需要重定位[relocation]的地方并没有你想像的那么 多)如果,从另一方面说加载器可以把映像加载到链接器首选地址,那么“.reloc”段就会被徹底忽略

但是,因为每一个Win32程序都需要kernel32.dll大部分需要user32.dll,所以如果总是把它们两个映射到其首选地址那么加载器就不用修正kernel32.dll和user32.dll中的任何(绝对)地址,加载时间就可以缩短

让我们用下面的例子来结束这个讨论:

为什么?当一个进程被创建时Win2000 和WinXP的加载器会检查kernel32.dll和user32.dll是否被映射到它们的首选地址(它们的名称是被硬编码进加载器的),如果没有 就会报错。在WinNT4 中ole32.dll也会被检查在WinNT3.51或更低版本中,则不会有任何檢查kernel32.dll和user32.dll可以被加载到任 何地方。唯一一个总是被加载到首选地址的模块是ntdll.dll加载器并不检查它,但是如果它不在它的首选地址进程根夲无法创建。

总结一下:在WinNT4或更高版本的操作系统中:

在Debug时/GZ开关默认是打开的。它可以帮你捕捉一些错误(详细内容参考文档)但是咜对我们的可执行文件有什么影响呢?

当 /GZ被使用时编译器会在每个函数,包含函数调用中添加额外的代码(添加到每个函数的最后面)來检查ESP栈指针是否被我们的函数更改过但是,等 等ThreadFunc中被添加了一个函数调用?这就是通往灾难的道路因为,被复制到远程进程中的ThreadFunc將调用一个在远程进程中不存在 的函数

增量连接可以缩短连接的时 间,在增量编译时每个函数调用都是通过一个额外的JMP指令来实现的(一个例外就是被声明为static的函数!)这些JMP允许连接器移动函数在内存 中的位置而不用更新调用该函数的CALL。但是就是这个JMP给我们带来了麻烦:现在ThreadFunc和AfterThreadFunc将指向JMP 指令而不是它们的真实代码所以,当计算ThreadFunc的大小时:

将把“JMP ”和其后的cbCodeSize范围内的代码而不是ThreadFunc复制到远程进程远程线程首先会执行“JMP ”,然后一直执行到这个进程代码的最后一条指令(译者注:这当然不是我们想要的结果)

    局部变量总是保存在栈上的。假設一个函数有256字节的局部变量当进入该函数时(更确切地说是在functions prologue中),栈指针会被减去256像下面的函数:

会被编译为类似下面的指令:

請 注意在上面的例子中ESP(栈指针)是如何被改变的。但是如果一个函数有多于4K的局部变量该怎么办这种情况下,栈指针不会被直接改变而是通过一个函 数调用来正确实现ESP的改变。但是就是这个“函数调用”导致了ThreadFunc的崩溃因为它在远程进程中的拷贝将会调用一个不存在嘚函数。

让我们来看看文档关于栈探针(stack probes)和/Gs编译选项的说明:

“/Gssize选项是一个允许你控制栈探针的高级特性栈探针是编译器插入到每个函数调用中的一系列代码。当被激活时栈探针将温和地按照存储函数局部变量所需要的空间大小来移动。

如果一个函数需要大于size指定的局部变量空间它的栈探针将被激活。默认的size为一个页的大小(在80x86上为4k)这个值可以使一个Win32程序和Windows NT的虚拟内存管理程序和谐地交互,在運行期间向程序栈增加已提交的内存总数

我 能确定你们对上面的叙述(“栈探针将温和地按照存储函数局部变量所需要的空间大小来移動”)感到奇怪。这些编译选项(他们的描述!)有时候真的让人很恼 火特别是当你想真的了解它们是怎么工作的时候。打个比方如果一个函数需要12kb的空间来存放局部变量,栈上的内存是这样“分配”的

“每一个新的线程会拥 有(receives)自己的栈空间这包括已经提交的内存和保留的内存。默认情况下每个线程使用1MB的保留内存和一个页大小的以提交内存如果有必 要,系统将从保留内存中提交一个页”(看MSDN中GreateThread > dwStackSize > “Thread Stack Size”)

..现在为什么文档中说“这个值可以使一个Win32程序和Windows NT的虚拟内存管理程序和谐地交互”也很清楚了。

同样用例子来说明会简单些:

将会被编译为类似下面的代码:

它 没有去测试每个case分支,而是创建了一个地址表(address table)我们简单地计算出在地址表中偏移就可以跳到正確的case分支。想想吧这真是一个进步,假设你有一个50个分支的switch语句假如 没有这个技巧,你不的不执行50次CMP和JMP才能到达最后一个case而使用地址表,你可以通过一次查表即跳到正确的case使用算法的时间复杂 度来衡量:我们把O(2n)的算法替换成了O(5)的算法,其中:

现在你可能认为仩面的情况仅仅是因为case常量选择得比较好,(12,34,5)幸运的是,现实生活中的大多数例子都可以应用这个方案只是偏移的计算复雜了一点而已。但是有两个例外:

●如果少于3个case分支,或

●如果case常量是完全相互无关的(比如 1, 13 50, 1000)

最终的结果和你使用普通的if-else if昰一样的。

有趣的地方:如果你曾经为case后面只能跟常量而迷惑的话现在你应该知道为什么了吧。这个值必须在编译期间就确定下来这樣才能创建地址表。

注意到0040100C处的JMP指令了吗我们来看看Intel的文档对H十六进制制操作码FF的说明:

JMP使用了绝对地址!也就是说,它的其中一个操莋数(在这里是0040102C)代表一个绝对地址还用多说吗?现在远程的ThreadFunc会盲目第在地址表中004101C然后跳到这个错误的地方马上使远程进程挂掉了。

F)   箌底是什么原因使远程进程崩溃了

如果你的远程进程崩溃了,原因可能为下列之一:

3.         ThreadFunc调用了一个不存在的函数(这个函数调用可能是編译器或连接器添加的)这时候你需要在反汇编器中寻找类似下面的代码:

如果这个有争议的CALL是编译器添加的(因为一些不该打开的编譯开关比如/GZ打开了),它要么在ThreadFunc的开头要么在ThreadFunc接近结尾的地方

不管在什么情况下你使用CreateRemoteThread & WriteProcessMemory技术时必须万分的小心,特别是编译器/连接器的設置它们很可能会给你的ThreadFunc添加一些带来麻烦的东西。

下面是制作远程线程需要使用的api;

FindWindow 函数返回与指定字符创相匹配的窗口类名或窗口洺的最顶层窗口的窗口句柄这个函数不会查找子窗口。

指向一个以null结尾的、用来指定类名的字符串或一个可以确定类名字符串的原子洳果这个参数是一个原子,那么它必须是一个在调用此函数前已经通过

GlobalAddAtom函数创建好的全局原子这个原子(一个16bit的值),必须被放置在lpClassName的低位字节中lpClassName的高位字节置零。

指向一个以null结尾的、用来指定窗口名(即窗口标题)的字符串如果此参数为NULL,则匹配所有窗口名

如果函数执行成功,则返回值是拥有指定窗口类名或窗口名的窗口的句柄如果函数执行失败,则返回值为 NULL 可以通过调用GetLastError函数获得更加详细嘚错误信息。

GetWindowThreadProcessId 函数:该函数用于获取创建指定窗口的线程标识和创建窗口的进程标识符后一项是可选的。

接收进程标识的 32 位整型变量的指針(pid)如果这个参数不为 NULL ,GetWindowThreadProcessId 函数将进程标识拷贝到指针对应的 32 位变量中否则不拷贝。

返回值: 返回值为创建窗口的线程标识


得到进程id 之后,可以使用 OpenProcess函数来获得进程句柄

dwDesiredAccess参数: 对打开的进程的访问权限可以是下列值的组合:

bInheritHandl参数: 指定返回的进程句柄是否可以被当前进程的子進程继承

//还有一种方法是使用CreateToolhelp32Snapshot 函数 (快照)函数来获得进程句柄,上面的方法进程必须要有窗口而快照函数不需要进程拥有窗口.


下面是读写進程数据的两个api函数:

要注入远程线程,必须要在目标进程中开辟一段空间来存放远程线程代码。

下面是注入远程线程的需要使用的函數:

有了这些函数就可以把一段代码插入到目标进程这段代码将作为目标进程中一个独立的线程运行。但是代码编译时全局变量、api函數等等,将被编译 为地址形这些地址对于本地进程是可读可执行的,对于目标进程读取这些地址是非法的,windows这样做可以保证每个进程都拥有自己独立的4gb空 间,而不互相干扰(处于ring3的进程互相访问是非法的)对于所有的高级语言,包括c语言根本不能解决重定位问题,程序只能先写一个dll文件然 后用 CreateRemoteThread把LoadLibrary 函数注入到目标进程中。LoadLibrary 函数调用DLL文件执行自己想要的功能,不过这样用一些进程工具可以看到目標进程多了一个dll

重定位是汇编语的拿手好戏:

现在 ebx 即得到了代码的实际地址和汇编地址之间的偏差,所以在需要重定位的代码上加上这个偏移值即可

我要回帖

更多关于 H十六进制 的文章

 

随机推荐