逆向入门(5)汇编篇-函数相关学习与JCC指令

0x01 函数

汇编眼中的函数,函数就是一系列指令的集合,为了完成某个会重复使用的特定功能。

可以使用JMP指令或者CALL指令来进行调用函数,先看JMP指令。

JMP指令调用函数

假设定义一个函数功能为将eax,ecx的值赋值为0,假设使用JMP来进行调用
逆向入门(5)汇编篇-函数相关学习与JCC指令
此时就会出现一个问题,当通过JMP调用了指令后,无法再次回到使用JMP指令的地方,解决的话可以在函数中再次使用JMP指令跳转回来。
逆向入门(5)汇编篇-函数相关学习与JCC指令
但是这样做同样也会出现问题,回想函数的定义,重复使用的特定功能,那么下次再进行函数时,仍然会回到首次定义的JMP地方,无法回到下次使用函数的地方,所以使用JMP指令来调用函数就不太方便。

CALL指令调用函数

这里再使用CALL指令来调用函数,由于CALL指令会将当前指令的下一行存储在堆栈中,所以直接在函数的最下面进行ret就可以回到之前执行函数的地方了。
逆向入门(5)汇编篇-函数相关学习与JCC指令
运行后观察结果
逆向入门(5)汇编篇-函数相关学习与JCC指令

函数的参数和返回值

以写一个加法的函数为例子

add eax,ecx
ret

这里的参数指的是就是eaxecx,返回值就是eax,如下
逆向入门(5)汇编篇-函数相关学习与JCC指令
运行结果后,eax应该为7,同时指针回到0040ef44,运行后观察结果。
逆向入门(5)汇编篇-函数相关学习与JCC指令


0x02 堆栈传参

如果在参数很多的情况下,计数器可能不够用情况,此时就可以用堆栈进行传递参数。

这里以计算5个参数值为例,先将值压入栈中

push 1
push 2
push 3
push 4
push 5

定义函数,此时应该要将最上层的栈的值给到eax中,然后连续让eax加上下面的几层栈存储的值

mov eax,dword ptr ds:[esp+4]
add eax,dword ptr ds:[esp+8]
add eax,dword ptr ds:[esp+C]
add eax,dword ptr ds:[esp+10]
add eax,dword ptr ds:[esp+14]
ret

运行测试
逆向入门(5)汇编篇-函数相关学习与JCC指令
效果正常实现

堆栈平衡

虽然上述实验成功实现效果,但是存在一个小问题,最后堆栈并没有还原,也就是所谓的没有堆栈平衡

上述程序在运行前,栈的最上面是12ffc4,但是函数运行结束后,则变成了12ffb0

针对上面的问题,第一个解决方案就是采用外平栈,在call指令后使用add esp,8就可以恢复栈的原有值了。

当然还可以直接将ret改为ret 8(等同于ret后再add esp,8),实现函数内的栈平衡,称为内平栈

esp寻址

从上面的例子可以看到最终拿出之前压入栈中的值时,是以esp为基址进行查找的,这种行为称为esp寻址。

mov eax,dowrd ptr ss:[esp+8]
add eax,dowrd ptr ss:[esp+4]
ret

这种寻址方式有非常明显的好处,因为esp寻找起来非常简单和直白。同样的,也是有存在缺点的。

假设某函数在使用时需要用寄存器,但是又无法将寄存器的值进行直接清空,需要保留,所以在执行函数前需要先保留寄存器中的值

push eax
push ecx
mov eax,dowrd ptr ss:[esp+8]
add eax,dowrd ptr ss:[esp+4]
ret

但是此时就会存在一个问题,由于push指令改变了栈,所以此时esp的值不能再直接去加了,而是要根据使用的指令情况来增加,这里由于使用了两个push,所以整体函数变成了

push ecx
push edx
mov eax,dowrd ptr ss:[esp+C]
add eax,dowrd ptr ss:[esp+10]
ret

同时在使用完ecxedx后也需要还原,所以还得继续使用pop做堆栈平衡。

push ecx
push edx
mov eax,dowrd ptr ss:[esp+C]
add eax,dowrd ptr ss:[esp+10]
pop edx
pop ecx
ret

从这个例子中也能看到缺点,如果之前push的指令比较多,影响了堆栈,那么在使用esp寻址时就需要手动计算esp的变更后的值,相对麻烦一些。

EBP寻址

从刚刚的情况中找到了不足,这里可以使用ebp来进行寻址,ebp是栈底指针。可以看下面的例子

push ebp
mov ebp,esp
sub esp,10

先将ebp的值存储栈中以便后续还原,将着将ebp设置到原有的esp的位置,接着减少esp的值,这样就可以重新扩展出一块堆栈了,使用时不会影响原有的栈。此时以ebp来寻址的话,就不会再重新计算参数的位置了,因为在使用堆栈的时候ebp的值是不会改变的。所以此时可以直接取值

mov eax,dword ptr ss:[ebp+4]
add eax,dowrd ptr ss:[bgp+8]

同时在完成函数后,还需要做平栈,还原ebpesp

mov esp,ebp
pop ebp
ret

虽然感觉多花了一些步骤,但是实际上如果函数步骤复杂,使用的堆栈较多的情况下,使用ebp寻址还是很有优势的。


0x03 JCC指令

有条件修改eip寄存器的指令,比如JMPCALL都是无条件修改。

JCC指令是通过查看标记寄存器来进行判断的
逆向入门(5)汇编篇-函数相关学习与JCC指令

  • CFcarry flag主要用来判断无符号数计算以后是否溢出,如果发生进位或者借位则将其置1,反之清零。
    逆向入门(5)汇编篇-函数相关学习与JCC指令
  • PFParity flag,如果结果的最低有效字节包含偶数个1位则置为1,否则清0,一般用于传递数值后的校验完整性
  • AFauxilary Carry flag,如果算术操作在结果的第3位发生进行或者进位,则为1,一般用于BCD运算。
  • ZFzero flag,如果运算结果为0,则置为1
    使用cmp或者test指令都会使用到此指令
    cmp可以判断两数是否相等(相当于sub,但是不把值进行存储)
    逆向入门(5)汇编篇-函数相关学习与JCC指令
    test可以判断否数是否为0(相当于and,也不存储数值)
    逆向入门(5)汇编篇-函数相关学习与JCC指令
  • SFSigh flag,有符号整数的最高有效位,0代表为正,1代表为负
  • OFOverflow flag,有符号数加减运算所得结果是否溢出,溢出为1,反之为0
    有符号数看of,无符号数看cf
  • DFdirection flag,方向位,控制栈的传递方向,比如movs,stos等指令,STDCLD指令分别 用于设置以及清除DF标志。

常见指令如下

JE,JZ,是结果为0则跳转,ZF=1
JNE,JNZ,是结果不为0则跳转,ZF=0
JS,结果为负则跳转,SF=1
JNS,结果为非负则跳转,SF=0
JP,JPE,结果中1的个数要是偶数则跳转,PF=1
JNP,JPO,结果中1的个数要是奇数则跳转,PF=0
JO:结果溢出则跳转,OF=1
JNO,结果未溢出则跳转,OF=0
JB,JNAE,是无符号数小于则跳转,CF=1
JNB,JAE,是无符号数大于等于则跳转,CF=0
JBE,JNA,是无符号数小于等于则跳转,CF=1 or ZF=1
JNBE,JA,是无符号数大于则跳转,CF=0 and ZF=0
JL,JNGE,是有符号数小于则跳转,SF!=OF
JNL,JGE,是有符号数大于等于则跳转SF=OF
JLE,JNG,是有符号数小于等于则跳转,ZF=1 or SF!=OF
JNLE,JG ,是有符号数大于则跳转 ZF=0 and SF=OF
上一篇:java开源物联网智能家居系统


下一篇:ESP32学习笔记(1)--VS code ESP-IDF 开发环境搭建