32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)

32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)

昨天,大家可能都看了代码了,不知道昨天有没有在汇编代码的基础上,实现注入计算器.

如果没有,今天则会讲解,不过建议把昨天代码熟悉一遍(课程是紧跟着来的,请不要拉下任何一天,因为今天的知识,

可能就和昨天的知识挂钩,昨天的知识,和前天的挂钩.....,当然你如你懂汇编,不是新手,那么则可以直接往下看)

一丶远程线程注入,和汇编远程注入的区别

昨天的代码,大家可能看了(没看也没有关系,就是远程线程注入的代码,开发角度,和汇编代码注入,底层角度的两份代码)

这里说下,他们的区别

首先我们知道,任何注入方式都有它们使用的特定场合

1.远程线程注入

  这个只针对软件的保护防范低使用的,病毒很少用这个,为什么

我们知道,远程线程注入,它会创建远程线程,进而加载我们的DLL,而对有保护的程序来说,它可能不能防范你使用CreateRemoteThread函数,但是可以针对你的dll,比如遍历dll个数

发现多了一个,程序就退出.等等.

2.汇编的远程注入

  汇编的远程注入,这个就有点狠了,为什么,因为你写的不是一个dll,而是一断汇编代码,写到对面的内存中,让他去执行,这样除非对面软件,试试的检测内存状态,否则不容易检测出自己程序的异常

当然汇编的远程注入,还是会开辟内存,但是我们知道,注入方法很多种,我们可以发挥想象,只重剑意不重剑招,我们可以这样想,你不是要申请内存吗,我们可以不申请内存,对面程序肯定会存再对齐的问题

,比如为了保证对齐,对面程序肯定会用NOP指令填充,那么我们则可以利用这块内存,这样软件除非也检测NOP,和对齐,否则你注入进去对面也发现不了,再比如对面软件很厉害,检测很到位,厉害到

对齐也检测了,那么我们可以把对面栈的内存抬高,把我们的程序代码写进去,对面总不注意试试的检测栈吧,执行完我们的代码就出栈,对面不可能检测栈的进出吧.所以重在想像(废话有点多,可以省略不看直接看下边 :) )

3.汇编远程注入代码分析(0D分析)

昨天我们因为时间关系,没有具体分析昨天的代码,今天我们用OD(OlleyDbg)一步一步分析,

这个分析也能让我们快速的掌握调试技巧

①分析FindWindow,看下汇编代码执行了什么

首先贴出我们昨天的代码

invoke FindWindow,NULL,offset g_szWindowName ,第二个参数是计算器的字符串
mov @hWnd, eax
.if eax == NULL
invoke ShowLastError
 ret
.endif
上面代码的逻辑:
寻找计算器,返回计算器的窗口句柄,如果成功,(返回值默认放eax中)
如果成功,继续往下执行,如果失败,调用ShowLastError显示错误信息

OD分析

这个是我们大体的FindWindow界面

我们查找下窗口,看下是否找到,找到则窗口句柄放在eax当中

我们可以看出,已经找到窗口了,并且窗口句柄已经在eax当中了,所以 eax == NULL 不成立,则跳转到下一条指令位置执行,而下一条指令位置,则开始调用GetWindowThreadProcessID了

②GetWindowThreadProcessID,获得进程的ID

首先还是对应着伪指令的汇编代码查看

invoke GetWindowThreadProcessId, @hWnd, addr @dwPID

代码很简单,我们知道,调用函数传参的时候,代码都是从右往左压栈的,所以第一个会 push dwPid,第二个会push hWnd

OD分析

因为走一步截次图太麻烦,而且影响大家观看,索性直接标号,把每一步写出来,这样大家自己调试,不懂的时候来看我的每一步代表什么意思

首先我把每一步执行的代码都用标号圈起来了

1. lea eax,[local,3] 意思是我要拿第三个局部变量,也就是栈的第三个局部变量,而我们以前说过,局部变量都是 ebp -xxx来获取,而现在是32位的汇编了,所以每个寄存器是4个字节,所以第三个局部变量则是-12 ,而对应局部变量

则是 -c

这句代码真实的代码则被翻译成了 

lea eax,dword ptr ss:[ebp - oxc]位置,我们就可以去栈中看下ebp -c的位置是什么了,注意这里因为我走到下边

所以已经获取到了进程PID的值,所以是810,默认的时候是0,那么是什么意思那,就是取得  ebp -c 的地址

2.取得ebp的地址(假设地址是18ff44)那么吧地址给eax,再把eax入栈,

3.把我们的第一个局部变量,也就是ebp - 4的值,(40D40,因为这里是中括号,所以对栈取内容得出的,而上面的是没有取内容,因为我们用的是lea指令)

4.调用GetWindowThreadProcessId,这个时候,因为我们把第二部的eax入栈(eax是ebx-c的栈地址),所以获得的PID

值则会给对应栈地址的内容(什么意思: 就是你提供局部变量的地址,也就是我们先前ebp -c的地址,操作系统获得PID的值,则会根据你给的地址,把对应地址里面的内容修改了,所以相当于是 mov  dword ptr[18ff44],810)

至此,我们可以总结下,这个GetWindowThreadProcessId,执行的过程

1.先获得局部变量的地址(ebp - c的地址,注意,不是值)

2.压栈

3.获得栈中第一个参数的值,(注意是栈地址里面的值,而不是栈地址)也就是上次获得的窗口的句柄

4.调用GetWindowThreadProcessId,把对应栈地址里面的值修改为我们的PID值(810),所以我们已经得到PID的值了

汇编代码:

invoke OpenProcess,PROCESS_ALL_ACCESS, FALSE, @dwPID
        mov @hProcess, eax
        .if eax == NULL
          invoke ShowLastError
          ret
        .endif首先,把PID压栈,然后把FALSE(汇编中是0)压栈,然后把权限压栈(权限就是常量)最后打开进程,如果成功获得进程句柄,则返回值放在eax中,把eax给局部变量然后判断局部变量是否==NULL,不想等继续走,相等就是打开失败,执行错误代码提示(ShowlastError)

OD分析

这个地方我也不用细讲了

1.首先,我们把进程的PID,也就是局部变量第三个(ebp - c里面的值)压栈

2.其次从右往左压入第二个参数,也就是FALSE

3.然后压入权限

4.调用OpenProcess

5.成功则eax保存的是进程的实例句柄

6.判断eax是否等于NULL,相等(获取失败)继续往下执行,调用Call  injectas.0040118b

④VirtualAllocEx,远程申请内存

汇编代码

invoke VirtualAllocEx,@hProcess, NULL, 1000h, MEM_COMMIT, PAGE_EXECUTE_READWRITE
        mov @lpBuff, eax
        .if eax == NULL
          invoke ShowLastError
          ret
        .endif

这个和上面一样,都是从右向左入栈,如果成功,返回在远程进程申请的内存的首地址,放在eax当中

失败则下面判断.

OD分析(注意,这种的上面都已经分析了很多遍了,API调用的传参,出栈,以及寄存器给局部变量赋值)

所以下方的API我会提供图片去看,但是不具体分析了,都是一样的,如果又兴趣的可以,自己练练手,手动分析,看下代码流程怎么执行.

已经成功了,肯定会执行,现在介绍OD的第二种用法

当一个应用程序被打开的时候,我们可以选择附加的方式,将这个程序挂起

现在我们把计算机附加,看下这个地方是否申请了内存

重新打开OD,现在是两个OD

,选择我们的计算器程序

搜索我们用Vir...申请成功的内存首地址,看看是否申请成功

申请成功,然后我们继续下一条指令执行,写内存数据到这里面

⑤,利用WriteProcessMemory写内存数据到这里面来

注意,我们写的使我们的INJECT_CODE的代码的二进制,所以程序在调用远程线程的时候,

会把我们的而二进制当做代码运行

看下汇编代码:

        invoke WriteProcessMemory,@hProcess, 
                                   @lpBuff, 
                                   INJECT_CODE, 
                                   start - INJECT_CODE, 
                                   NULL

传参,什么的不说了,这里需要注意一下 要写入的内容是我们刚才申请的内存首地址

现在给的是lpBuff,也就是我们往哪里写,(往计算机器我们申请的哪块内存写,所以lpbuff就是计算器这块内存的首地址了)

写入的数据是 INJECT_CODE的代码的二进制

写入的大小是START - INJECT_CODE数据的大小

INJECT_CODE是在START标号的上面,我们看下

汇编代码,和OD分析

OD分析

可以看到,我的标号

1.表示我们要写入对面内存的起始地址(也就是我们用vir申请的)

2.我们要写入的缓冲区,也就是我要写入inject为开始,开始把这块内存写入

3.写入的大小就是我们计算出来的START- INJECT_CODE

4.实际写入的字节我们不关心,关于START - INJECT_CODE我们看下代码开始出就明白了

相当于00401017 - 00401000 = 17个字节,所以要写入17个字节

看下计算机程序中,有没有写入我们的二进制代码

正好17个字节,而且代码也写进去了

最后我们调用CreateReomteThread开始把INJECT_CODE当做代码去执行了,这里传参和上面一样

不在分析了

至此,分析到这里就完了,下面写代码就不分析了,开始真正的写汇编代码注入的程序了,因为汇编代码和上面大同小异都是调用API,而后API传参.保存返回值给局部变量,出栈等等都是一样的,所以下方开始真正写.如果感性区,想提升自己的调试能力,以及对OD的熟练程度,可以自己去分析一下

 二丶汇编注入代码的编写,以及应该注意的各种问题

首先,如果做过昨天作业的同学应该知道,会遇到对面代码和我方代码的的位置不一样

比如

我们INJECT_CODE的位置,和对面INJECT_CODE代码的位置

还有就是DLL加在的位置不同,也会影响API的调用

比如我们代码在INJECT_CODE里面调用一个MessageBox,他可以弹窗

但是要注意,在对面的那边调用这个就会出错,为什么

所以我们要注意几个问题

1.Call的时候问题

2.地址重定位问题

首先是1问题

①Call的时候的问题

我们在汇编代码中随便看一个Call 然后按下空格键,看下汇编是什么

我们分别在自己程序的INJECT_CODE 和对面程序的INJECT_CODE看下执行MessageBox会出现什么问题

这个是汇编代码

看下OD

我们可以看到都是调用0x401204,但是结果是正确的吗

我们用在反汇编窗口 CTRL + G 跳转到00401204 我们发现

第一个程序,也就是我们的注入程序,它调用MessageBox,是有的

而计算器的程序调用的时候,是没有的,找不到这块内存,所以就出错了

为什么会出现这个问题,这个就是著名的重定位问题,以前我们DLL注入的时候,是系统帮我们重定位了

而现在我们要自己去重定位这个问题

首先我们知道,任何程序运行的时候,都会加在ntdll, 而kernel32.dll也会加载,user32.dll也会加载

而kernel32.dll并不是必须加载的,但是%99.999的程序都会加载这个dll ((*^▽^*))

user32.dll是和用户相关的,也会有%99的加载

那么就产生一个问题,看下图

 我们注入程序调用MessageBox会从user32.dll中找到MessAgebox的地址,并且调用

而B程序,显然DLL的首地址是2000的位置,首先不说我们能不能调用它

就我们刚才看Call的时候,他是直接call了一个常量 00401204,而显然,这块内存是不属于B进程的所以出错了

他是属于A进程的,

所以我们要重定位API地址

怎么定位

1.获得当前注入程序的User32.dll的加载的实例句柄

2.并且创建进程快照遍历计算机器进程模块User32.dll的实例句柄

然后看下图

首先注入程序得出1000h  ,远程的程序得到的user32.dll的模块地址是2000h

3.获得MessageBox距离模块的偏移,注意,这个偏移获取出来,是两方都一样的,因为函数的位置都是一样的,只有

模块地址加载的位置不一样

看图

算出来的都是100, 所以我们就有了一个公式 

 函数首地址 - 模块首地址  = 得出了函数距离模块的实际偏移

然后远程模块 + 函数距离模块的实际偏移,得出远程进程的Messagebox的实际偏移

假设我们本地进程是1000h  Messagebox的距离是1100

那么  1100 (函数首地址) - 模块首地址(1000) = 实际偏移(100)

然后   远程模块地址(2000) + 实际偏移(100) = 实际函数地址

这个公式请熟练记住

看字不明白,看图:

先看公式,再看箭头指向

那么基于这个公式我们就开始写我们的汇编代码了

现在的函数地址重定义问题已经解决了,但是注意,只是函数地址的重定位

下面写完汇编代码,就明白,另一个函数调用地址无关性的重定位问题了,

也就是我们要解决的第二个问题,说的有点多,看代码,其实代码很简单

LOCAL @hLocalUser32Module:MODULE        ;存放本地User32.dll的模块地址
LOCAL @hRemoteUser32Module:MODULE      ;存放远程user32.dll的模块地址
;1.调用GetModule加载user32.dll获得user32.dll的模块地址
invoke GetModule,offset g_szUser32         ;g_szUser32看做字符串user32.dll,就是dll需要这个字符串,去寻找user32.dll,如果想看完整工程
请下载每天资料查看
mov @hLocalUser32Module,eax                ;返回值存放user32.dll模块地址,给局部变量 @hRemoteUser32Module,eax; 按理说这里应该遍历被注入进程的模块
;获得user32.dll的地址,但是这里我的都是同系统,所以dll位置是一样的,如果你把这个注入程序给另外一个系统就要自己遍历了,遍历代码就不写了,和调用API
;一样,如果不会写,可以下方评论.
mov 
;2.获得MessageBox函数的地址
invoke GetProcess,@hLocalUser32Module,offset g_szMsgbox
sup eax,@hLocalUser32Module          ;函数地址 - 模块地址 = 实际偏移
mov ebx,hRemoteUser32Module         ; 把另外进程的句柄给ebx
add ebx,eax                   ;  远程模块地址 + 实际偏移 = 远程函数实际偏移位置
这里给ebx是为了中转一下计算
lea eax,MSG_BOX               ;求出inject标号所在的位置
mov[eax],ebx                   ;写入另外实际函数地址并且调用

对于最后两个,求出标号所在的位置,和写入实际函数地址并且调用

这个则是在我们的INJECT_CODE里面,新申请了一个标号位置,

然后里面的内存写入的使我们的实际地址

但是现在我们发现出现了新的问题

虽然我们已经写给了MSG_BOX,但是还是不能正常运行

为什么我们写进去的代码确实是MSG函数的地址

但是我们要知道,我们现在并不知道代码执行的位置在哪里

比如INJECT_CODE 中我们要CALL这个MSG_BOX的地址

你会发现,CALL的时候MSG_BOX还是一个全局常量,也就是说,你API地址的重定位问题已经解决了,现在的

代码重定位还没有解决

看下图理解:

现在我们也计算出来了API的地址了,但是地址还没有计算出来,这个时候大家会问,我们不是遍历了dll模块的地址了吗,把它拿过来用不行吗,可以,但是问题不在这,你拿过来也是也内存,但是只要你在INJECT_CODE里面call的时候

都不是call的它,而是你在本地进程call的,给远程内存写过去了,远程也call,call的也是一个地址,而这个地址压根不存在,那么就会出错.

比如:

我们要计算的是代码和我们的代码call的位置的偏移

也就是 inject_code 和我们下方写的代码的偏移

算这一段距离,但是这个你在远程进程中也不好算,所以就有了新的方法

看汇编代码

push ebp
    push ebx
    call $+5 ;CALL 下行指令
TEXT:
    pop  ebp  ;地址重定位
    sub  ebp, offset TEXT

首先保存栈环境,ebp,我们下方会用到ebx,也保存,

call $+5是什么意思, 一起就是call指令占五个字节,在call的时候会把下一条指令入栈,也就是TEXT指令位置入栈

而下方紧接着pop ebp,这个比较重要了,主要为了什么,我们主要为了拿到IP的位置

试想一下,CALL 一次会把下面的地址入栈,然后出栈就得到了当前IP执行代码的地址了

对不对

紧接着我们又写了

sub ebp,offset TEXT,这个是为了什么,我们想一下,在我们本进程,offset TEXT会被翻译为一个常量

当我们ebp减去TEXT位置,就得到了ebp和代码位置处的偏移了,看图

想想一下,另外一个进程减掉我们的本地的偏移,得到一个偏移,是不是相当于另外一个进程也得到自己代码的位置了,然后我方用ebp + 函数的偏移位置  得出函数地址  相当于对面的程序 减去我们的地址 加上 函数位置的偏移

也是一样的调用函数地址

不明白看图,这里比较绕,但是很重要:

2000 - 1000 = 1000 这个1000是地址重定位 也就是代码的位置在这里,而后加上函数的偏移= 实际执行代码的位置

也就是远程线程也是这样的

代码都在课堂代码连接中,请下载观看

课堂代码连接: 链接:http://pan.baidu.com/s/1bprSUcf 密码:rsag

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏章鱼的慢慢技术路

MFC绘图基础——上机操作步骤

56930
来自专栏程序员互动联盟

【专业技术第五讲】动态链接库及其用法

存在的疑惑: 动态链接库到底如何来使用?特别是windows上面 解决方案: 本篇我们讲Windows上的动态链接库(Dynamic Link Library ...

31970
来自专栏编程思想之路

WiFiAp探究实录--功能实现与源码分析

Android虐我千百遍,我待Android如初恋。 ——————编辑于2017-08-02——————— wifi热点说的是wifiAp相...

1.8K90
来自专栏我的博客

ThinkPHP3.1.2笔记

1.开启trace 方法一:在配置文件中添加(默认在config.php,如果定义debug模式,可以定义在debug.php) SHOW_PAGE_TRAC...

27380
来自专栏北京马哥教育

MongoDB多纬度监控方法详解

一、mongostat工具方法 mongostat是mongdb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如...

49350
来自专栏程序员宝库

PHP 面试知识梳理

算法与数据结构 BTree和B+tree BTree B树是为了磁盘或者其他存储设备而设计的一种多叉平衡查找树,相对于二叉树,B树的每个内节点有多个分支,即多叉...

449120
来自专栏IT派

PHP面试知识梳理

B树是为了磁盘或者其他存储设备而设计的一种多叉平衡查找树,相对于二叉树,B树的每个内节点有多个分支,即多叉。

37830
来自专栏木头编程 - moTzxx

微信小程序Ⅵ [wx.request 的回调使用]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

1.3K30
来自专栏SDNLAB

OpenFlow协议库开发者指南

介绍 OpenFlow协议库是OpenDaylight的一个组件,调解OpenDaylight controller和支持OpenFlow协议的硬件设备之间通...

66580
来自专栏前端那些事

Express4.x API (三):Response (译)

Express4.x API 译文 系列文章 技术库更迭较快,很难使译文和官方的API保持同步,更何况更多的大神看英文和中文一样的流畅,不会花时间去翻译--,所...

180100

扫码关注云+社区

领取腾讯云代金券