汇编语言预备知识
指令简介与总览
| 汇编指令 | 指令格式 | 功能 | 备注 |
|---|---|---|---|
| lea | lea reg, addr |
主要用于将一个内存地址加载到寄存器中, 而不是将该地址处的数据加载到寄存器。 |
如在printf函数中,将格式字符串的地址加载到指定寄存器中供printf函数读取。 |
| inc | inc reg |
寄存器自增1 | 变量加一 |
| mov | mov reg, imm |
将立即数赋值到寄存器中 | imm意为立即数(immediate number) |
| imul | 指令格式见详解 | 用于执行带符号整数乘法。 | 它可以接受一个、两个或三个操作数,具体取决于你想要执行的乘法类型。 |
| mul | mul source |
用于执行无符号整数乘法。 | mul 指令只接受一个操作数,这个操作数可以是寄存器或内存地址。根据操作数的大小(8位、16位、32位或64位),乘积会被存储在特定的寄存器组合中。 |
| shl/shr | shl/shr dest, cnt |
逻辑左/右移 | shr用于将寄存器或内存中的二进制数值向右移动指定的位数。这个操作会将每个位都向右移动,并在左侧补0。对于无符号数来说,这相当于除以2的幂次;而对于有符号数,它仅当被移位的数据视为无符号时有效,因为逻辑右移不会复制符号位。 |
| sar | sar dest, count |
算术左/右移 | sar指令用于将指定的二进制数按算术右移的方式移动指定的位数。算术右移是指在右移时,最高位(符号位)保持不变,低位移出,高位补上符号位的值。这通常用于有符号数的除以2的幂次操作。 |
| cdq | cdq |
将32位寄存器eax中的有符号数符号扩展为64位,结果存储在由edx:eax组成的64位寄存器中 |
它会把eax的第31位(符号位)复制到edx的每一位。如果eax的符号位是0(即正数),则edx会被设置为0;如果符号位是1(即负数),则edx会被设置为全1。通常在执行32位有符号除法(如 idiv指令)之前使用,以确保被除数的符号位正确扩展到高位寄存器,从而保证除法运算的正确性。 |
| cqo | cqo |
将64位寄存器rax中的有符号数符号扩展为128位,结果存储在由rdx:rax组成的128位寄存器中 |
它会把rax的第63位(符号位)复制到rdx的每一位。如果rax的符号位是0,则rdx被设置为0;如果符号位是1,则rdx被设置为全1。在64位模式下,通常在执行64位有符号除法之前使用,确保被除数的符号位正确扩展到高位寄存器。 |
imul - 带符号整数乘法
一个操作数
imul src
这种形式假设隐含的目标操作数是累加器(对于16位操作为AX,对于32位操作为EAX,对于64位操作为RAX)。该指令将累加器与source相乘,并将结果存储回累加器和其对应的双倍宽度寄存器(即对于16位操作为DX:AX,对于32位操作为EDX:EAX,对于64位操作为RDX:RAX)。
两个操作数
imul dest, source
这种形式将source与dest相乘,并将结果存储在dest中。dest可以是一个寄存器或内存位置,而source可以是一个寄存器、内存位置或立即数。注意,结果被截断到目标操作数的大小。
三个操作数
imul dest, source1, source2
这种形式将source1与source2相乘,并将结果存储在dest中。source1和source2可以是寄存器、内存位置或立即数,但dest必须是寄存器。结果同样被截断到目标操作数的大小。
mul - 无符号整数乘法
mul的功能
MUL是一个单操作数指令,它隐式地使用AL(8位)、AX(16位)、EAX(32位)或RAX(64位)作为其中一个乘数,具体取决于操作数的大小和处理器模式。
对于字节乘法(8位),结果被存储在AX寄存器中;对于字乘法(16位),结果被存储在DX:AX寄存器对中;对于双字乘法(32位),结果被存储在EDX:EAX寄存器对中;对于四字乘法(64位),结果被存储在RDX:RAX寄存器对中。
mul的示例
; 8
mov al, 0x05 ; AL = 5
mov bl, 0x03 ; BL = 3
mul bl ; AX := AL * BL
; AL = 5 * 3 = 15 (0x0F), AH = 0
; 16
mov ax, 0x0123 ; AX = 291
mov bx, 0x0004 ; BX = 4
mul bx ; DX:AX := AX * BX
; AX = 291 * 4 = 1164 (0x048C), DX = 0
; 32
mov eax, 0x12345678 ; EAX = 305419896
mov ebx, 0x00000002 ; EBX = 2
mul ebx ; EDX:EAX := EAX * EBX
; EAX = 305419896 * 2 = 610839792 (0x2468ACF0), EDX = 0
scanf与printf函数
scanf会使用多个寄存器:rcx,rdx,r8等等,均存储输入项的地址,其中rcx为格式化字符串的地址。
printf使用的寄存器与scanf类似。
逆向分析程序1
使用IDA打开reverse_basic.exe,反汇编结果如下:
; int __fastcall main(int argc, const char **argv, const char **envp)
main proc near
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= qword ptr -10h
arg_0= qword ptr 8
arg_8= qword ptr 10h
arg_10= qword ptr 18h
; __unwind { // __GSHandlerCheck
mov [rsp+arg_0], rbx
mov [rsp+arg_8], rbp
mov [rsp+arg_10], rsi
push rdi
sub rsp, 30h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rsp+38h+var_10], rax
lea r8, [rsp+38h+var_18]
lea rdx, [rsp+38h+var_14]
lea rcx, aDD ; "%d %d"
call sub_140001080
mov ecx, [rsp+38h+var_18]
mov eax, 38E38E39h
add ecx, [rsp+38h+var_14]
imul r8d, ecx, 7
lea rcx, Format ; "%d\n"
lea ebx, ds:0[r8*8]
lea edi, [rbx+rbx*8]
mul edi
mov esi, edx
mov edx, r8d
shr esi, 1
mov ebp, esi
shr ebp, 3
call sub_140001020
mov edx, ebx
lea rcx, Format ; "%d\n"
call sub_140001020
mov edx, edi
lea rcx, Format ; "%d\n"
call sub_140001020
mov edx, esi
lea rcx, aU ; "%u\n"
call sub_140001020
mov edx, ebp
lea rcx, aU ; "%u\n"
call sub_140001020
mov eax, 24924925h
lea rcx, aU ; "%u\n"
mul ebp
sub ebp, edx
shr ebp, 1
add edx, ebp
shr edx, 2
call sub_140001020
xor eax, eax
mov rcx, [rsp+38h+var_10]
xor rcx, rsp ; StackCookie
call __security_check_cookie
mov rbx, [rsp+38h+arg_0]
mov rbp, [rsp+38h+arg_8]
mov rsi, [rsp+38h+arg_10]
add rsp, 30h
pop rdi
retn
; } // starts at 1400010E0
main endp
可以发现该程序的结构大致是,进行一些运算之后,集中打印运算结果。其中sub_140001020即为prinf函数,sub_140001080为scanf函数。
lea r8, [rsp+38h+var_18]
lea rdx, [rsp+38h+var_14]
lea rcx, aDD ; "%d %d"
call sub_140001080
因此此程序会读取两个四字节数据写入var_14和var_18。
mov ecx, [rsp+38h+var_18]
mov eax, 38E38E39h
add ecx, [rsp+38h+var_14]
imul r8d, ecx, 7
分析接下来的指令,程序将读取的两个数相加之后乘以7,将结果存到r8d中。
lea rcx, Format ; "%d\n"
lea ebx, ds:0[r8*8]
lea edi, [rbx+rbx*8]
mul edi
mov esi, edx
mov edx, r8d
shr esi, 1
mov ebp, esi
shr ebp, 3
call sub_140001020
lea ebx, ds:0[r8*8]通过地址计算的方式计算了r8 * 8的值存入ebx中,然后类似地计算rbx * 9存入edi中。
然后经过其他无关运算后,调用printf将r8d(乘以7的结果)打印出来(先将r8d赋给edx)。
mov edx, ebx
lea rcx, Format ; "%d\n"
call sub_140001020
mov edx, edi
lea rcx, Format ; "%d\n"
call sub_140001020
这一段打印出ebx和edi。
mov edx, esi
lea rcx, aU ; "%u\n"
call sub_140001020
mov edx, ebp
lea rcx, aU ; "%u\n"
call sub_140001020
根据这两个打印可以知道,在上面的运算中,esi和ebp被存入了运算结果,让我们再次分析上面被我们忽视的部分指令:
mov eax, 38E38E39h
...
lea rcx, Format ; "%d\n"
lea ebx, ds:0[r8*8]
lea edi, [rbx+rbx*8]
mul edi
mov esi, edx
mov edx, r8d
shr esi, 1
mov ebp, esi
shr ebp, 3
call sub_140001020
mul edi的作用:会计算edi * eax,将结果存入edx: eax中,eax被赋值为38E38E39h,是编译器将除以9运算进行优化使用的魔数。因此esi为edi / 9。然后将esi存入ebp后右移3位。
mov eax, 24924925h
lea rcx, aU ; "%u\n"
mul ebp
sub ebp, edx
shr ebp, 1
add edx, ebp
shr edx, 2
call sub_140001020
24924925h是除以7的魔数,因此这里计算ebp除以7后打印出来。
综上,我们可以复原这个可执行文件的原c文件:
#include <stdio.h>
int main(int argc, char *argv[]) {
int a, b, c;
scanf("%d %d", a, b);
c = a + b;
int n1 = c * 7;
int n2 = n1 * 8;
int n3 = n2 * 9;
unsigned int n4 = n3 / 9;
unsigned int n5 = n4 / 8;
unsigned int n6 = n5 / 7;
printf("%d\n", n1);
printf("%d\n", n2);
printf("%d\n", n3);
printf("%u\n", n4);
printf("%u\n", n5);
printf("%u\n", n6);
return 0;
}
逆向分析程序2
; int __fastcall main(int argc, const char **argv, const char **envp)
main proc near
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= qword ptr -10h
arg_0= qword ptr 8
; __unwind { // __GSHandlerCheck
mov [rsp+arg_0], rbx
push rdi
sub rsp, 30h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rsp+38h+var_10], rax
lea r8, [rsp+38h+var_14]
lea rdx, [rsp+38h+var_18]
lea rcx, aDD ; "%d %d"
call sub_140001080
mov eax, [rsp+38h+var_14]
lea rcx, Format ; "%d\n"
mov edi, [rsp+38h+var_18]
lea ebx, [rdi+rax]
imul edi, eax
lea edx, [rdi+rbx]
call sub_140001020
mov eax, 66666667h
imul [rsp+38h+var_18]
sar edx, 1
mov ecx, edx
shr ecx, 1Fh
add edx, ecx
lea rcx, Format ; "%d\n"
call sub_140001020
mov r8d, [rsp+38h+var_14]
lea rcx, Format ; "%d\n"
lea edx, [r8+r8*4]
call sub_140001020
sub ebx, [rsp+38h+var_18]
lea rcx, Format ; "%d\n"
mov edx, ebx
call sub_140001020
mov eax, edi
lea rcx, Format ; "%d\n"
cdq
idiv [rsp+38h+var_14]
mov edx, eax
call sub_140001020
xor eax, eax
mov rcx, [rsp+38h+var_10]
xor rcx, rsp ; StackCookie
call __security_check_cookie
mov rbx, [rsp+38h+arg_0]
add rsp, 30h
pop rdi
retn
; } // starts at 1400010E0
main endp
lea r8, [rsp+38h+var_14]
lea rdx, [rsp+38h+var_18]
lea rcx, aDD ; "%d %d"
call sub_140001080
容易知道,这里使用scanf函数读入两个数据存入var_18, var_14中。
mov eax, [rsp+38h+var_14]
lea rcx, Format ; "%d\n"
mov edi, [rsp+38h+var_18]
lea ebx, [rdi+rax]
imul edi, eax
lea edx, [rdi+rbx]
call sub_140001020
这段首先将var_14和var_18分别存入eax和edi中。计算ebx = rdi + rax,计算edi = edi * eax, 计算edx = rdi + rbx。即为计算edx = var_14 + var_18 + var_14 * var_18然后打印出来。
mov eax, 66666667h
imul [rsp+38h+var_18]
sar edx, 1
mov ecx, edx
shr ecx, 1Fh
add edx, ecx
lea rcx, Format ; "%d\n"
call sub_140001020
这里是使用魔数66666667h优化除法运算。实际上计算的是var_18 / 5。
mov r8d, [rsp+38h+var_14]
lea rcx, Format ; "%d\n"
lea edx, [r8+r8*4]
call sub_140001020
容易知道这里计算的是var_14 * 5,使用var_14 * 4 + var_14优化。
sub ebx, [rsp+38h+var_18]
lea rcx, Format ; "%d\n"
mov edx, ebx
call sub_140001020
mov eax, edi
lea rcx, Format ; "%d\n"
cdq
idiv [rsp+38h+var_14]
mov edx, eax
call sub_140001020
ebx在之前存储着var_18 + var_14的结果,因此这里计算ebx - var_18后打印。
edi存储着var_18 * var_14的结果,这里计算var_18 * var14 / var_14后打印。
源码:
#include <stdio.h>
int main() {
int var_18, var_14;
scanf("%d %d", &var_18, var_14);
int a = var_18 + var_14;
int b = var_18 * var_14;
printf("%d\n", a + b);
printf("%d\n", var_18 / 5);
printf("%d\n", var_14 * 5);
printf("%d\n", a - var_18);
printf("%d\n", b / var_14);
return 0;
}