• 1. Stay - Post
  • 2. Circles - Post
  • 3. Hollywood's_Bleeding - Post
  • 4. A_Thousand_Bad_Times - Post

从0开始学习的二进制安全(一)--基础流程

前言

二进制安全一直都说入门难,但因为当需要了解内核相关的东西时,有需要有一定的二进制知识,因此决定从0开始学习二进制安全,在了解了一定的汇编知识后,通过ctf题来快速加深印象,因此选择了非常友好的攻防世界新手题。

Hello,CTF解题

总之先拖到IDA里面去看看,首先当然是查看main函数了,使用F5进行反汇编:

以上为函数主体,我们从C语言角度来看的话,该函数做了这样的事情:

由此我们需要判断出比较的16进制字符串是什么,通过解码得到结果如下:

返回:

最后flag就是这串字符CrackMeJustForFun,不过一般开程序之前先进行查壳操作,否则反编译时的行为,可能会触发反编译防护机制,导致自己的主机受损。

汇编角度

做CTF题最主要是熟悉汇编,因此我们从汇编的角度来看这道题,我们从IDA的函数视图出发,先看开头:

我们先了解一下5个寄存器:

根据《汇编语言》三版这本书,我们知道AX,BX,CX,DX是四个基本的通用寄存器这里也是同理,虽然是通用寄存器,但也有些许不同。前面e的前缀相当于extension,将原来的AX系列16位的寄存器,扩展到32位。

  1. eax,累加器寄存器,其中a表示accumulator也就是累加的意思,是很多加法乘法指令的缺省寄存器。
  2. ebx,基地址寄存器,其中b 表示base也就是基地址的意思,是在内存寻址时存放基地址的寄存器。
  3. ecx,计数器寄存器,其中c表示counter计数,做重复操作或循环的内定计数器。
  4. edx,与累加寄存器相对,其中d表示division除法,存储用于除操作后的余数。
  5. ebp,保存进入函数时这一时刻的esp,即栈顶指针。

接下来出现了两个寄存器,通常在字符串变量使用:

  1. esi,源寄存器,通常在使用字符串相关操作时使用,s表示source源的意思,因此这个寄存器通常指向字符串操作的源字符串。
  2. edi,目的寄存器,和esi相对,d应该表示Destination目的,因此这个寄存器通常只想字符串操作的目标字符串。

出现了两个常见关键词:

  1. offset,功能是取得相应符号的偏移量,比如:

    假设a的偏移量位0040400H

    a BYTE
    b WORD
    c DWORD
    d DWORD
    >>>>>
    mov esi, OFFSET a -----> ESI=0040400H
    mov esi, OFFSET b -----> ESI=0040401H
    mov esi, OFFSET c -----> ESI=0040403H
    mov esi, OFFSET d -----> ESI=0040407H

    这里变量 db 变量的方式等同于int i=1定义变量。

    1. lealea指令是mov指令的地址计算版,该指令可以直接将地址付给寄存器。

有了上述知识,我们可以知道了,此处做了什么事情了:

首先给定一个栈大小,通过操纵栈顶寄存器esp的位置控制栈大小,为ecx寄存器赋值,与后面的rep指令有关。

压入ebx寄存器在栈底,用于保存进入函数的基地址,方便返回主函数。

压入ebp,保存进入函数时的栈顶地址,方便返回主函数。

压入esi,用于保存字符操作的源地址串。

压入edi最后入栈,因此在栈顶,用于保存目标字符串。

lea edi,[esp+70h+var_24]
等同于
lea edi, [esp+4Ch]

然后取得变量a437261636b4d65的偏移量,将该值交给esi寄存器。

通过rep movsd一次传一个DWORDedi所指向的内存单元,共传输8DWORD

上述操作应该对应反汇编中的strcpy函数的相应操作。

接着再看下一段汇编:

和源码对照一下:

然后我们进行逐行分析,首先是:

mov ecx,8 

与后面rep有关,每次repecx寄存器值减1

xor eax,eax

进行了异或操作,异或的结果会被保存到eax中进行存储,这里获得了初始化数组中的初始化值,即0.

lea edi [esp+70h+var_48]

该汇编语句经过ida转换后,正好是

lea     edi, [esp+28h]

一直因此这里是将要转换的目标字符串地址给edi

push    offset aPleaseInputYou ; "please input your serial"

将字符串压入栈内,然后进行

rep stosd
stosw
stosb

这里涉及三个指令:

  1. stosd,将eax的内容存储到edi所指向的内存单元中(Dword 有四个字节,因此只有eax才能存储)
  2. stosw,将ax的内容存储到edi指向的内存单元中(Word 有两个字节,ax就能够存储了)
  3. stosb,将al的内容存储到edi所只想的内存单元中(Byte 只需要ax中的alah就能存储,但通常使用al)

由此我们知道,eax存储着初始化的0值,lea将指定的数组内存地址赋值给edi后,由rep stosdeax中的内容填充到edi所指向的内存空间,由于rep关键词,因此会将该内存空间传8DWORD,刚好是char[32]的长度。

由此上述汇编(除push字符外)实际上对应了:

将v10数组,用0填充进行初始化。

call    sub_40134B

此步对应了print("please input your serial")操作,实际进入sub_40134B函数体中,是将字符串赋到stout上,这里就不作详细分析了。

后面从静态分析非常难理解,因此我们从od动态调试来帮助我们理解,此处逻辑:

edi中的地址指向我们输入的内容,这里可以判断出scanf函数给输入分配的地址为[esp+0x18],接着ecx按位与0xFFFFFFFF进行了或计算,此时ecx中的内容变为了全1

经过进一步的运算,esp被指向了一个模块,下面一个坨,是一个计算字符串长度的汇编代码:

or      ecx, 0FFFFFFFFh 设置循环次数为-1
xor     eax, eax 设置搜索内容为0
add     esp, 0Ch 调整栈顶指针的位置
repne scasb 一直重复搜索指导搜索到\0
not     ecx 得到搜索次数
dec     ecx 减1得到字符串不包含\0的字符长度 
//到这一步为止实际上实现了源代码中的strlen()函数
cmp     ecx, 11h (11h=17)比较字符串长度是否为17,然后进入后续判断的跳转。
//if判断语句

涉及到的关键词:

  1. repne,当重复CX≠0ZF=0时执行后续操作。
  2. scasb,根据edi内存单元的内容扫描字符串的字节
  3. not,进行取反操作
  4. decdec ecx等同于sub eax, 1
  5. or,按位进行与计算
  6. xor,按位进行异或计算

总结

简单来说,我们通过一道简单题的题了解了逆向分析的一般步骤,采用动静态分析的方式,当静态分析出现难点,或者不明白具体含义时,可以考虑使用OD进行动态分析,帮助自己理解程序的意思。


除非注明,ebounce文章均为原创,转载请以链接形式标明本文地址

本文地址:https://www.ebounce.cn/web/77.html

新评论

captcha
请输入验证码