【武大软件逆向课程/第四周】加减乘除运算的逆向特征

汇编语言预备知识

指令简介与总览

汇编指令 指令格式 功能 备注
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

​ 这种形式将sourcedest相乘,并将结果存储在dest中。dest可以是一个寄存器或内存位置,而source可以是一个寄存器、内存位置或立即数。注意,结果被截断到目标操作数的大小。

三个操作数

imul dest, source1, source2

​ 这种形式将source1source2相乘,并将结果存储在dest中。source1source2可以是寄存器、内存位置或立即数,但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会使用多个寄存器:rcxrdxr8等等,均存储输入项的地址,其中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_14var_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中。

​ 然后经过其他无关运算后,调用printfr8d(乘以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

​ 这一段打印出ebxedi

mov     edx, esi
lea     rcx, aU         ; "%u\n"
call    sub_140001020
mov     edx, ebp
lea     rcx, aU         ; "%u\n"
call    sub_140001020

​ 根据这两个打印可以知道,在上面的运算中,esiebp被存入了运算结果,让我们再次分析上面被我们忽视的部分指令:

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运算进行优化使用的魔数。因此esiedi / 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_18var_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_14var_18分别存入eaxedi中。计算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;
}
星藏点雪,月隐晦明
Built with Hugo
Theme Stack designed by Jimmy