逆向入门(7)汇编篇-mul指令的学习
在做一个题的时候发现了mul
指令,自己写的kengen
和OD
里面断下来的状态不同,最后定位出了是mul
指令的原因。简单记录下,提高印象。
0x01 mul基础概念
32
位模式下整数乘法可以实现32
、16
或8
位的操作,64
位下还可以使用64
位操作数。
MUL
执行无符号乘法,IMUL
执行有符号乘法
MUL
:无符号数乘法
32
位模式下,MUL
(无符号数乘法)指令有三种类型:
- 执行
8
位操作数与AL
寄存器的乘法; - 执行
16
位操作数与AX
寄存器的乘法; - 执行
32
位操作数与EAX
寄存器的乘法
MUL
指令中的单操作数是乘数。下表按照乘数的大小,列出了默认的被乘数和乘积。由于目的操作数是被乘数和乘数大小的两倍,因此不会发生溢岀,换句话说,两个8
位二进制数的乘积不会超过16
位
0x02 实际体验
#include <iostream>
#include <string>
#include <cstdint>int main()
{std::string username;// 获取用户名输入std::cout << "用户名: ";std::getline(std::cin, username);int userLen = username.length();if (userLen < 4) {printf("用户名长度过短");return 0;}int result = 1;for (int i = 0; i < userLen;i++) {result *= username[i];printf("0x%x\n", result);}return 1;
}
上面的代码是根据汇编自己写的算法,但是最后结果和预期结果不同,预期比下列结果要大2
。
最终在汇编中发现多了一段加上edx
的操作,之前因为edx
一直为0
,就忽略了一段。然后下面通过ai
修改过的代码,主要是使用了无符号整数
#include <iostream>
#include <string>
#include <cstdint> // 包含 uint32_t 和 uint64_tint main() {std::string username;std::cout << "用户名: ";std::getline(std::cin, username);if (username.length() < 4) {std::cout << "用户名长度过短";return 0;}uint32_t eax = 1; // 模拟EAX寄存器uint32_t edx = 0; // 模拟EDX寄存器(高位)for (char c : username) {uint8_t ecx = static_cast<uint8_t>(c); // 当前字符的ASCII值if (ecx == 0) break; // 遇到空字符终止(如汇编中的OR ECX,ECX/JE)// 模拟 MUL ECX 指令(32位乘法)uint64_t product = static_cast<uint64_t>(eax) * ecx;// 分离高低32位(EDX:EAX)edx = static_cast<uint32_t>(product >> 32); // 高32位eax = static_cast<uint32_t>(product); // 低32位// 模拟 ADD EAX, EDXeax += edx;// 输出当前状态(与调试器对比)printf("字符 '%c' (0x%02X): EAX=0x%08X, EDX=0x%08X\n", c, ecx, eax, edx);}std::cout << "最终结果: EAX = 0x" << std::hex << eax << ", EDX = 0x" << edx << std::endl;return 0;
}
这一次就是汇编正确的效果了,结果相差为2
0x03 分析原因
根本原因:在计算到字符n
时,累乘值eax
已经非常大110,996,500
,乘以 n
的 ASCII
值110
后,结果12,209,615,000
超过了 32 位无符号整数的最大值(2³² - 1 = 4,294,967,295)
。这导致 mul
指令产生溢出,结果的高 32
位存储在 edx
中。
数值计算:12,209,615,000 ÷ 4,294,967,296 ≈ 2.842
,因此商(高位部分)为 2
,余数(低位部分)为 3,619,680,408
。这就是 edx = 2
的直接原因。
循环行为:在之前的迭代中,乘积结果均小于 2³²
,因此 edx
保持为 0
。只有在最后一次乘法时,数值足够大,才触发 edx
非零。