从简单破解学到的汇编基础

一些基础汇编的解析

Posted by WuJ on March 21, 2020

从简单破解学到的汇编基础

基本汇编指令复习

lea 与 mov 与 []

  1. lea 指令

    第二个操作数可以是寄存器或者变量

    若为寄存器:必须加 [] ,相当于取值操作

    mov eax, 2
    lea ebx, [eax]		;执行后 ebx = 2
    mov ebx, eax			;等同于上句
    lea ebx, eax			;编译器报错: error A2070: invalid 			    instruction operands
    

    优点:

    lea 可以作简单的算术计算,特别是有了32位指令的增强寻址方式,比如要算EAX*4+EBX+3,结果放入 EDX,怎么办?

    mov edx, eax
    shl edx, 2
    add edx, ebx
    add edx, 3

    现在用 lea 一条指令搞定: lea edx, [ebx + eax * 4 + 3]

    注意:

    ​ 在函数执行时,经常会看到这种指令:lea esi, [ebp-30] 用于获取局部变量的地址;注意不能用 OFFSET 获得堆栈参数的地址,因为 OFFSET 只适用于编译时已知的地址。下面的语句无法汇编:

    mov esi,OFFSET [ebp-30 ] ;错误

    若为变量:有无 [] 都一样,为取地址操作,相当于 C语言中的 &

    num dword 2
    lea ebx, num
    lea eax, [num]		; eax 为 num 的地址,如 eax = 4206598,随程序不同不同,这时 ebx = eax
    
  2. mov 指令

    若第二个操作数为变量:有无 [] 都一样

    num dword 2
    mov eax, 2
    mov ebx, num
    mov ecx, [num]		;执行完 ebx = ecx = 2
    

    若第二个操作数为寄存器:加 [] 表示将其内存地址中的值传值

    mov ecx,[eax]			; 相当于 mov ecx, ds:[eax],将内存地址为 eax 处的值传给 ecx
    

set 指令

作用为根据EFLAGS寄存器中的状态标识设置目标操作数的值为 0 或 1。目标操作数指向一个字节寄存器或内存中的一字节。

sete(setz) al		; 取当前标志寄存器中 ZF 的值,放到 AL 中
setne(setnz) al  ; 取得 ZF 值后,取反,再放到 AL 中。
其中 e 表示 equal (相等)

经常接在会改变标志寄存器的指令之后,这样就省去了条件跳转

cmov 指令

条件传送表示要满足某个或某些条件时才执行传送数据。

条件传送传送数据的格式为: cmovx src, dst

前面的字母c为英文condition(意思是条件)的首字母,后面的x表示条件,不同的条件有不同的表示,通常是一到三个字母

cmovne/cmovnz 表示 ZF = 0 时传送数据

mov eax, 2
cmp eax, 2
cmovne ebx, 3 ; 因为 cmp 指令执行后,ZF = 1,因此 3 不会被传给 ebx
即不相等时才传值

参考链接:条件传送数据cmov指令

05 初试破解-控制台 汇编程序解读

其实稍微有点经验的一看就知道这是个密码验证过程,且一下就能发现密码的 ascii

但我还是想把每一条指令都搞明白,记录一下

(因为 OD 版本不同,所以这里指令看上去和视频里不一样,这个无所谓)

(假设用户输入就是密码 rkvir)

lea eax,[local.14]	; 取局部变量的地址到 eax
push eax						; 将 eax 压栈
push 0046D6B8       ; 压入C语言的格式符 %s
call scanf					; 获取用户输入,以上这就是函数调用的过程
mov eax,[local.14]	; 将局部变量的值传给 eax
xor edx,edx					
add esp,0x18				; 平栈
cmp al,0x72					; 比较密码的第一位,ZF=1
sete dl							; edx = 1
cmp ah,0x6B					; 比较第二位,ZF=1
lea ecx,dword ptr ds:[edx+0x1]	; ecx = 1+1 = 2
cmovne ecx,edx			; 因为 ZF=1,因此不传值,ecx 不变
cmp byte ptr ss:[ebp-0x36],0x76		; 比较第三位,ZF=1
lea eax,dword ptr ds:[ecx+0x1]		; eax = 2+1 = 3
cmovne eax,ecx			; 因为 ZF=1,因此不传值
cmp byte ptr ss:[ebp-0x35],0x69 	; 同上
lea ecx,dword ptr ds:[eax+0x1]
cmovne ecx,eax
cmp byte ptr ss:[ebp-0x34],0x72
lea edx,dword ptr ds:[ecx+0x1]
cmovne edx,ecx
cmp byte ptr ss:[ebp-0x33],0x0
lea eax,dword ptr ds:[edx+0x1]
cmovne eax,edx
cmp eax,0x5

这里一开始不明白为什么一个简单的比较字符串的程序这么冗长,后来才发现这就是故意设置的,其中 ecx edx eax 相互增加的过程就是原C程序的 nflag 增加的过程。如果某一位密码错误,eax 都不会顺利地增加到 6,则登录失败