—
本帖被 步行南街 从 和平精英 移动到本区(2019-06-24)
—
最近,有不少刚学习内联汇编的网友和同学跟我聊,他写的内联汇编代码总是出现一些莫名其妙的错误,看上去明明是对的,却怎么也执行不成功。
甚至有的是,代码注入器都可以测试成功的汇编代码,复制到VC里不行了!感到很困惑。
那么,今天我们就一起来探讨一下这个问题,帮助刚学习内联汇编的同学和刚逆向入门的同学少走些弯路,少遇一些坑。
[attachment=10285957]
我们先来看下什么是内联汇编?
内联汇编,指在C语言中插入汇编语言。
内联汇编可以帮我们做什么?
1.可以帮我们在C语言中插入汇编代码。
2.可以帮我们调用目标程序的子程序(CALL)。
3.可以协助我们偷功能。
2和3 是可以做一些羞羞的事的,功能很强大
[attachment=10285959]
我们先来看一个简单的例子
[attachment=10285960]
下面的反汇编窗口是一个小游戏的吃药函数
push的第一个参数是 push 0
push的第二个参数是 push edx
edx 是表示的药品位置(当然正常情况 我们不会选择这样参数的函数调用,位置参数一定是没有ID 参数调用方便的,这里只是随便举一个简单例子)
call 004FACD0 只是为了取一个返回值eax
然后把返回值赋值给 参数ECX
即mov ecx,eax
最后调用函数 call 00491C50
函数我们分析清楚了,就可以写代码了
首先先在代码注入器中编写代码
代码注入器中测试成功的代码(药品我们选择第一个位置的 ,edx 为0 )
我们看到除了变化的参数我们需要给其赋值以外,其他的代码就照抄的
然后我们到VC中进行内联汇编的编写
这里需要注意的只有两点
第一点,从反汇编窗口复制出来的数值都是16进制的,到VC里要加上个0x
例如 004F1CD0 写成 0x004F1CD0
第二点,call 立即数 语法是不合法的 需要用寄存器转一下
call 0x00491C50 写成 mov eax,0x00491C50 call eax
上面的例子可以看出内联汇编写法很容易
__asm{},把自己想插入的汇编代码直接按照汇编的语法编写即可。
感觉完了?不,才开始 =。=
以上只是最简单的情况,在我们编写的过程中可能存在很多错误,主要表现为三种错误。
第一种错误,编译错误
这种错误编译器会提示我们,这种错误没什么难度,如下
不能直接call 立即数
标点符号要英文的
立即数不能作为左值
根据提示修改即可。
第二种,运行错误,当然编译器是不会管我们的,所以我们就要额外小心的使用内联汇编。
我们拿个例子看下
例如代码注入器测试成功的代码
我们按照规则修改成内联汇编代码如下
也编译通过了
但是这段内联汇编代码测试了好多次
调用一定是会崩溃程序的(代码注入器的完全没有问题)
为什么?
那我们可以选择逆向我们自己的代码来看看情况,由于我写的是MFC dll
需要注入到游戏中运行的,所以我们附加上这个小游戏 ,然后去看我们的代码
首先我们在自己的内联汇编中加一些特征 ,方便我们直接定位跳转过去。
这样的特征,正常代码 是打死也不会有的。
点开E
[attachment=10286109]
找到我们的模块 我们的DLL.dll
入口地址 37542D16
CTRL+G 跳过去
回车来到模块领空
CTRL + S 搜索我们的特征码
找到了我们的内联汇编代码
我们来对比一下代码
是不是发现 我们代码 mov ecx,[0x00D0DF1C] 的括号没有了
有的人会说, 这是你没有加 dword ptr
那么我们加一下试试
发现代码依然是这样的
那么怎么办?
我们怎么写才能不出错误
代码写成这样就可以了
这是根本看不出来的可能的错误,一个是靠我们已经有的经验解决
另外一个就是,我们出现问题 ,用上面的方法到游戏里定位问题,解决问题,永远是最好的方法。
第三种,可以预知的运行错误
看上去没有什么错误,但是实际上这样运行是百分百会崩溃的。
原因很简单,汇编代码不合法!
有的同学会说哪里不合法啊?
我们看int a[3] = {7,键码,1}; 一个数组,在逆向的本质里,其实就是3个局部变量而已
[ebp-4],[ebp-8],[ebp-C]
而 a是这个数组的首地址 即ebp-C
那么我们看 下面的内联汇编中
push a 是否合法
push a 等价于 push ebp - C
那么当然是不合法的!
因为不合法 ,编译器 不会编译出现不合法的代码的
他强制将代码编程成 push [ebp-c] 这样参数含义完全变化,程序执行过程自然出现错误,导致崩溃也是必然的了。
想要解决
所以我们要改成以下代码
正确的方法一
我们 lea ebx,a[0]
相当于 lea ebx,[ebp-C]
然后再 push ebx
这样就合法了
如下图
正确的方法二
把指针转换成 用局部变量存放 然后再PUSH
总之,使用内联汇编,一定要对其机制比较了解
否则稍不小心,会在上面浪费很多调试时间
处于好奇心,我也百度了一下,看看是不是也有很多人出现过类似的问题,果然找到了几个,我已经对其进行了一一解答。简单截图2个具有代表性的问题 我们一起看一眼
1.
上面的例子,就是我们第二种错误的情况
代码需要写成
push 0
mov eax,0x1E22DD8
mov eax,[eax]
push eax
push 0
这样就可以了
2.
上面的例子,就是我们第三种错误的情况
编译器认为代码不合法,强行编译的错误
并不是内联汇编不把字符串名当指针看待,而是你的汇编代码不合法,
编译器为了能够编译,编译结果自然和你想的不是一样的了。
a 是字符串名,看上去 mov ebx,a 就可以了。
但是 真要是编程出来了,你还认识这样的汇编代码吗? mov ebx, ebp-xx ????
他真要编译出来是不是你认都不敢认,没见过这样的代码啊!
所以他只能编译成合法的代码吧。 那就是mov ebx,[ebp-xx], 也就是你所谓的,把指针硬当成变量看,编译器心里苦啊,是您逼迫我这样干的!
偷功能
偷功能其实和写call 没有什么本质的区别了,只是需要抄下来的代码更多一些
更确切的说是复制粘贴出来修改的代码更多一些。
复制出来的汇编代码需要进行初步处理才能写到内联汇编中
例如常数全部要加上0x
例如,call xxxxx 都要用寄存器转一下
例如 mov eax,[0x12345678] 这种代码也要转一下
等等
还有一个最重要的,就是跳转要修改
例如 jnz 12345678 他是要跳转到目的代码地址执行的,我们偷出来的代码已经不是原来的地址了
所以要通过标签修改跳转的地址。
跳转的写法
我们随便截取一段代码,来说明跳转怎么修改
内联汇编中的写法
__asm
{
mov esi,[eax]
cmp esi,eax
Je Label1 // 不能再写 je 0x66C66F
Label2: // 跳转过来的标签
mov edi,esi
mov esi,[esi]
push 0
lea ecx, [edi+8]
mov eax,0x0066CC60
call eax
push 1
push edi
mov ecx,ebp
mov eax,0x0066cc20
call eax
cmp esi,[ebp]
Jnz Label2 // 不能再写 je 0x66C652
Label1: // 跳转过来的标签
mov eax,[ebp]
}
我们查看自己的代码看是否正确
发现是正确的
当然偷功能的时候
我们尽量偷完整的函数代码,而不是中间一段
并且要传递正确的参数,才能执行成功。
绝对地址跳转
这样就可以了
如果是JMP 这样写就可以了
因为我们知道 jmp 的操作数可以是寄存器
而JNZ EAX 是不合法的
jnz 等条件跳转怎么绝对地址跳转呢
不可以
我们增加代码 继续尝试
发现无论你后面立即数写多少 都是没用的
他只是跳到下一条
因为 立即数 都被编译成了 00000000
目前已经知道解决方案有3个
一个是用标签
一个是自己判断数据,进行跳转
最后一个方法就是 自己计算跳转值 在代码里进行修改了,这种方式推荐裸函数,容易计算偏移
现在确实是不对的
那么我们 自己改写即可
0x371C5960 到 0x371C5984 是0x24字节 加上 0F84 的两字节
偏移是 0x26字节
也就是说 我们要修改的地址 是从函数头部+0x26的位置
我们修改成的值 是 跳转目标 - 本条代码地址 -5
既下面代码
DWORD temp = 0x12345678 - ((DWORD)&aaa+0x24) -5 ;
然后把值写入 函数头部+0x26的位置
*(DWORD*)((DWORD)&aaa+0x26) = temp;
调用以后的效果
相当于串改了代码,不过是自己的代码
值得注意的是,如果函数有Jmp 跳转需要再加入计算
先讲到这里,欢迎补充和探讨!
甚至有的是,代码注入器都可以测试成功的汇编代码,复制到VC里不行了!感到很困惑。
那么,今天我们就一起来探讨一下这个问题,帮助刚学习内联汇编的同学和刚逆向入门的同学少走些弯路,少遇一些坑。
[attachment=10285957]
我们先来看下什么是内联汇编?
内联汇编,指在C语言中插入汇编语言。
内联汇编可以帮我们做什么?
1.可以帮我们在C语言中插入汇编代码。
2.可以帮我们调用目标程序的子程序(CALL)。
3.可以协助我们偷功能。
2和3 是可以做一些羞羞的事的,功能很强大
[attachment=10285959]
我们先来看一个简单的例子
[attachment=10285960]
下面的反汇编窗口是一个小游戏的吃药函数
push的第一个参数是 push 0
push的第二个参数是 push edx
edx 是表示的药品位置(当然正常情况 我们不会选择这样参数的函数调用,位置参数一定是没有ID 参数调用方便的,这里只是随便举一个简单例子)
call 004FACD0 只是为了取一个返回值eax
然后把返回值赋值给 参数ECX
即mov ecx,eax
最后调用函数 call 00491C50
函数我们分析清楚了,就可以写代码了
首先先在代码注入器中编写代码
代码注入器中测试成功的代码(药品我们选择第一个位置的 ,edx 为0 )
我们看到除了变化的参数我们需要给其赋值以外,其他的代码就照抄的
然后我们到VC中进行内联汇编的编写
这里需要注意的只有两点
第一点,从反汇编窗口复制出来的数值都是16进制的,到VC里要加上个0x
例如 004F1CD0 写成 0x004F1CD0
第二点,call 立即数 语法是不合法的 需要用寄存器转一下
call 0x00491C50 写成 mov eax,0x00491C50 call eax
上面的例子可以看出内联汇编写法很容易
__asm{},把自己想插入的汇编代码直接按照汇编的语法编写即可。
感觉完了?不,才开始 =。=
以上只是最简单的情况,在我们编写的过程中可能存在很多错误,主要表现为三种错误。
第一种错误,编译错误
这种错误编译器会提示我们,这种错误没什么难度,如下
不能直接call 立即数
标点符号要英文的
立即数不能作为左值
根据提示修改即可。
第二种,运行错误,当然编译器是不会管我们的,所以我们就要额外小心的使用内联汇编。
我们拿个例子看下
例如代码注入器测试成功的代码
我们按照规则修改成内联汇编代码如下
也编译通过了
但是这段内联汇编代码测试了好多次
调用一定是会崩溃程序的(代码注入器的完全没有问题)
为什么?
那我们可以选择逆向我们自己的代码来看看情况,由于我写的是MFC dll
需要注入到游戏中运行的,所以我们附加上这个小游戏 ,然后去看我们的代码
首先我们在自己的内联汇编中加一些特征 ,方便我们直接定位跳转过去。
这样的特征,正常代码 是打死也不会有的。
点开E
[attachment=10286109]
找到我们的模块 我们的DLL.dll
入口地址 37542D16
CTRL+G 跳过去
回车来到模块领空
CTRL + S 搜索我们的特征码
找到了我们的内联汇编代码
我们来对比一下代码
是不是发现 我们代码 mov ecx,[0x00D0DF1C] 的括号没有了
有的人会说, 这是你没有加 dword ptr
那么我们加一下试试
发现代码依然是这样的
那么怎么办?
我们怎么写才能不出错误
代码写成这样就可以了
这是根本看不出来的可能的错误,一个是靠我们已经有的经验解决
另外一个就是,我们出现问题 ,用上面的方法到游戏里定位问题,解决问题,永远是最好的方法。
第三种,可以预知的运行错误
看上去没有什么错误,但是实际上这样运行是百分百会崩溃的。
原因很简单,汇编代码不合法!
有的同学会说哪里不合法啊?
我们看int a[3] = {7,键码,1}; 一个数组,在逆向的本质里,其实就是3个局部变量而已
[ebp-4],[ebp-8],[ebp-C]
而 a是这个数组的首地址 即ebp-C
那么我们看 下面的内联汇编中
push a 是否合法
push a 等价于 push ebp - C
那么当然是不合法的!
因为不合法 ,编译器 不会编译出现不合法的代码的
他强制将代码编程成 push [ebp-c] 这样参数含义完全变化,程序执行过程自然出现错误,导致崩溃也是必然的了。
想要解决
所以我们要改成以下代码
正确的方法一
我们 lea ebx,a[0]
相当于 lea ebx,[ebp-C]
然后再 push ebx
这样就合法了
如下图
正确的方法二
把指针转换成 用局部变量存放 然后再PUSH
总之,使用内联汇编,一定要对其机制比较了解
否则稍不小心,会在上面浪费很多调试时间
处于好奇心,我也百度了一下,看看是不是也有很多人出现过类似的问题,果然找到了几个,我已经对其进行了一一解答。简单截图2个具有代表性的问题 我们一起看一眼
1.
上面的例子,就是我们第二种错误的情况
代码需要写成
push 0
mov eax,0x1E22DD8
mov eax,[eax]
push eax
push 0
这样就可以了
2.
上面的例子,就是我们第三种错误的情况
编译器认为代码不合法,强行编译的错误
并不是内联汇编不把字符串名当指针看待,而是你的汇编代码不合法,
编译器为了能够编译,编译结果自然和你想的不是一样的了。
a 是字符串名,看上去 mov ebx,a 就可以了。
但是 真要是编程出来了,你还认识这样的汇编代码吗? mov ebx, ebp-xx ????
他真要编译出来是不是你认都不敢认,没见过这样的代码啊!
所以他只能编译成合法的代码吧。 那就是mov ebx,[ebp-xx], 也就是你所谓的,把指针硬当成变量看,编译器心里苦啊,是您逼迫我这样干的!
偷功能
偷功能其实和写call 没有什么本质的区别了,只是需要抄下来的代码更多一些
更确切的说是复制粘贴出来修改的代码更多一些。
复制出来的汇编代码需要进行初步处理才能写到内联汇编中
例如常数全部要加上0x
例如,call xxxxx 都要用寄存器转一下
例如 mov eax,[0x12345678] 这种代码也要转一下
等等
还有一个最重要的,就是跳转要修改
例如 jnz 12345678 他是要跳转到目的代码地址执行的,我们偷出来的代码已经不是原来的地址了
所以要通过标签修改跳转的地址。
跳转的写法
我们随便截取一段代码,来说明跳转怎么修改
内联汇编中的写法
__asm
{
mov esi,[eax]
cmp esi,eax
Je Label1 // 不能再写 je 0x66C66F
Label2: // 跳转过来的标签
mov edi,esi
mov esi,[esi]
push 0
lea ecx, [edi+8]
mov eax,0x0066CC60
call eax
push 1
push edi
mov ecx,ebp
mov eax,0x0066cc20
call eax
cmp esi,[ebp]
Jnz Label2 // 不能再写 je 0x66C652
Label1: // 跳转过来的标签
mov eax,[ebp]
}
我们查看自己的代码看是否正确
发现是正确的
当然偷功能的时候
我们尽量偷完整的函数代码,而不是中间一段
并且要传递正确的参数,才能执行成功。
绝对地址跳转
这样就可以了
如果是JMP 这样写就可以了
因为我们知道 jmp 的操作数可以是寄存器
而JNZ EAX 是不合法的
jnz 等条件跳转怎么绝对地址跳转呢
不可以
我们增加代码 继续尝试
发现无论你后面立即数写多少 都是没用的
他只是跳到下一条
因为 立即数 都被编译成了 00000000
目前已经知道解决方案有3个
一个是用标签
一个是自己判断数据,进行跳转
最后一个方法就是 自己计算跳转值 在代码里进行修改了,这种方式推荐裸函数,容易计算偏移
现在确实是不对的
那么我们 自己改写即可
0x371C5960 到 0x371C5984 是0x24字节 加上 0F84 的两字节
偏移是 0x26字节
也就是说 我们要修改的地址 是从函数头部+0x26的位置
我们修改成的值 是 跳转目标 - 本条代码地址 -5
既下面代码
DWORD temp = 0x12345678 - ((DWORD)&aaa+0x24) -5 ;
然后把值写入 函数头部+0x26的位置
*(DWORD*)((DWORD)&aaa+0x26) = temp;
调用以后的效果
相当于串改了代码,不过是自己的代码
值得注意的是,如果函数有Jmp 跳转需要再加入计算
先讲到这里,欢迎补充和探讨!