从0开始学习的二进制安全(一)--基础流程
前言
二进制安全一直都说入门难,但因为当需要了解内核相关的东西时,有需要有一定的二进制知识,因此决定从0开始学习二进制安全,在了解了一定的汇编知识后,通过ctf
题来快速加深印象,因此选择了非常友好的攻防世界新手题。
<!--more-->
Hello,CTF解题
总之先拖到IDA里面去看看,首先当然是查看main函数了,使用F5进行反汇编:
以上为函数主体,我们从C语言角度来看的话,该函数做了这样的事情:
由此我们需要判断出比较的16进制字符串是什么,通过解码得到结果如下:
返回:
最后flag就是这串字符CrackMeJustForFun
,不过一般开程序之前先进行查壳操作,否则反编译时的行为,可能会触发反编译防护机制,导致自己的主机受损。
汇编角度
做CTF题最主要是熟悉汇编,因此我们从汇编的角度来看这道题,我们从IDA的函数视图出发,先看开头:
我们先了解一下5个寄存器:
根据《汇编语言》三版这本书,我们知道AX,BX,CX,DX
是四个基本的通用寄存器这里也是同理,虽然是通用寄存器,但也有些许不同。前面e的前缀相当于extension
,将原来的AX
系列16位的寄存器,扩展到32位。
eax
,累加器寄存器,其中a
表示accumulator
也就是累加的意思,是很多加法乘法指令的缺省寄存器。ebx
,基地址寄存器,其中b
表示base
也就是基地址的意思,是在内存寻址时存放基地址的寄存器。ecx
,计数器寄存器,其中c
表示counter
计数,做重复操作或循环的内定计数器。edx
,与累加寄存器相对,其中d
表示division
除法,存储用于除操作后的余数。ebp
,保存进入函数时这一时刻的esp
,即栈顶指针。
接下来出现了两个寄存器,通常在字符串变量使用:
esi
,源寄存器,通常在使用字符串相关操作时使用,s
表示source
源的意思,因此这个寄存器通常指向字符串操作的源字符串。edi
,目的寄存器,和esi
相对,d
应该表示Destination
目的,因此这个寄存器通常只想字符串操作的目标字符串。
出现了两个常见关键词:
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
定义变量。lea
,lea
指令是mov
指令的地址计算版,该指令可以直接将地址付给寄存器。
有了上述知识,我们可以知道了,此处做了什么事情了:
首先给定一个栈大小,通过操纵栈顶寄存器esp
的位置控制栈大小,为ecx
寄存器赋值,与后面的rep
指令有关。
压入ebx
寄存器在栈底,用于保存进入函数的基地址,方便返回主函数。
压入ebp
,保存进入函数时的栈顶地址,方便返回主函数。
压入esi
,用于保存字符操作的源地址串。
压入edi
最后入栈,因此在栈顶,用于保存目标字符串。
lea edi,[esp+70h+var_24]
等同于
lea edi, [esp+4Ch]
即
然后取得变量a437261636b4d65
的偏移量,将该值交给esi
寄存器。
通过rep movsd
一次传一个DWORD
给edi
所指向的内存单元,共传输8
个DWORD
上述操作应该对应反汇编中的strcpy
函数的相应操作。
接着再看下一段汇编:
和源码对照一下:
然后我们进行逐行分析,首先是:
mov ecx,8
与后面rep
有关,每次rep
后ecx
寄存器值减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
这里涉及三个指令:
stosd
,将eax
的内容存储到edi
所指向的内存单元中(Dword
有四个字节,因此只有eax
才能存储)stosw
,将ax的内容存储到edi
指向的内存单元中(Word
有两个字节,ax
就能够存储了)stosb
,将al的内容存储到edi
所只想的内存单元中(Byte
只需要ax
中的al
或ah
就能存储,但通常使用al)
由此我们知道,eax
存储着初始化的0
值,lea
将指定的数组内存地址赋值给edi
后,由rep stosd
将eax
中的内容填充到edi
所指向的内存空间,由于rep
关键词,因此会将该内存空间传8
个DWORD
,刚好是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判断语句
涉及到的关键词:
repne
,当重复CX≠0
且ZF=0
时执行后续操作。scasb
,根据edi
内存单元的内容扫描字符串的字节not
,进行取反操作dec
,dec ecx
等同于sub eax, 1
or
,按位进行与计算xor
,按位进行异或计算
总结
简单来说,我们通过一道简单题的题了解了逆向分析的一般步骤,采用动静态分析的方式,当静态分析出现难点,或者不明白具体含义时,可以考虑使用OD进行动态分析,帮助自己理解程序的意思。