一、第一次creakme
1.什么是creakme
- 现在有一个creakme.exe可执行文件,我们执行它,点击help–register–需要输入密码和账号,但是现在我不知道此程序的账号和密码是什么,当我们输入一个错误的账号和密码时,会弹出一个新的窗口,提示"no luck there",现在我们要做的是:我们想让程序弹正确输入账号密码的框,所以我们要逆向破解一下此程序
2.逆向分析此程序所需要的知识
- PE结构(任何一个可执行程序都应该满足PE结构,我们要根据先分析出此程序的组成部分和结构)
- 下断点(一个程序的执行应该是连贯的,当时有时候我们需要在程序执行的中间某过程中停下,我们要分析这个过程的源头或者信息或者下一步的操作,则需要给程序下断点)
- win32 API(一个程序能再windows上跑起来,执行某些弹框等功能,其实就是调用了win32 API函数)
- 什么是函数调用(我们要知道函数是什么,到底怎么调用的,跟程序有什么关系)
- 熟悉堆栈,画过堆栈图(有些程序的信息和数据是存储在某时刻的堆栈中的某地址中的,所以我们需要了解堆栈在一些函数调用前中后到底存储了一些什么信息以及存在了堆栈中的哪个位置)
- call指令 JCC 标志寄存器(这个指令信息、容器的意思是什么,对程序有什么作用)
3.第一次逆向的思路
-
我们打开此exe文件,输入错误账号密码后会弹出显示错误,利用win32 API知识,可以知道这个弹窗其实就是一个MessageBox函数
-
所以我们现在想要分析一下这个弹窗是怎么弹的,为什么会弹,即找到执行弹窗的代码或者指令的部分。于是我们要在弹MessageBoxA之前设置断点,让MessageBoxA将要执行之前就把程序停下来
在OD命令行窗口输入
bp MessageBoxA
,接着查看B
,当中会显示一条信息表示已经在MessageBox前下了断点 -
接着我们需要让程序跑起来做相关操作让程序弹窗,以至于让程序执行到断点处停下。
点击OD上面的执行,执行程序,接着输入账号和密码,让程序弹窗,但我们设了断点,所以此时OD会停在断点处,这里就是MessageBox起始地址
-
接着我们根据堆栈和函数调用的知识,在函数调用之前,会将此函数调用完后的返回地址存入栈顶,即此MessageBox函数调用完后会返回到栈顶中存入的地址,接着执行,所以我们要去看看栈顶中的这个地址。追踪过去后,上面不远处应该就有一个call指令,此MessageBox函数就是这条指令调用的,因为调用了此函数,那个窗口才会弹出来
右键栈顶中的值–Follow in DIsassemble–即追踪到这个地址跳转过去
-
此时我们就找到了函数所在的位置,那么由于我们写代码时我们一般会这样写:判断账号和密码与正确的账号密码是否匹配,不匹配则调用显示错误信息的函数,如果匹配则调用显示正确信息的函数。所以我们应该找到程序判断的代码在哪个地址,即何种情况会调用错误信息函数,如果找到调用了错误的,那么那个地方的附近应该就有何时会调用输入正确的函数。那么我们在显示输入错误提示功能的函数开头设置断点(把刚才那个断点先取消),让程序重新执行,执行到显示输入错误提示功能的函数时就停下,然后找此时堆栈中的信息
双击显示输入错误提示功能的函数地址设置断点,打开
B
,将刚在MessageBoxA设置的断点的Active栏中按一下空格,将此断点的状态从Always变成Disabled,即取消断点 -
跟上面的同理此时栈顶中存放的值就是此显示输入错误提示功能的函数调用完后的返回地址,那么再次追踪地址,找到判断账号密码是否正确的指令地址。这个地址上面的附近指令可以找到
call
指令,再上面就是JE
,JE就是JCC中的一个,JCC是因为什么来跳转的:标志寄存器! -
JCC的本质:JCC后面会跟一个地址,是根据什么来跳转的呢?是看标志寄存器。而
JE
则是看标志寄存器Z
:如果Z
为0则不跳转继续执行后面的指令;如果Z
为1则跳转。所以只需要在程序执行到JE
这里的指令时停下来,将标志寄存器改为1,接着执行,则程序就会调用显示正确信息的函数,进而再调用正确信息的MessageBoxA的弹窗函数。所以再在JE
指令地址双击一下设置断点,再将刚才设置的断点取消,再一次重新执行程序,输入账号和密码,此时程序就会停在刚才设置断点的JE
处不是看上面的CMP指令,虽然有关系,因为CMP指令有一个比较的意思,我们猜测可能是比较输入的账号或密码和正确的账号或密码,如果正确则将标志位变为1,则下面执行
JE
时就会跳转;如果错误则标志位为0,则接着执行JE
时就不会跳转,接着执行下面的指令。但是现在不管那么多!反正我们只要让它跳转就行,不要执行到下面调用错误信息提示函数的call指令 -
我们将标志寄存器
Z
的值改为1,此时就已经改变了程序执行的流程!成功让程序弹正确输入账号密码的框!所以JCC和标志寄存器太重要了,如果想让程序去哪,执行到哪,就要吃透标志寄存器
-
但是注意:此时只是在内存中改了程序的标志寄存器,当再一次执行此程序时,还是不好使。那么此时我们只需要将
JE
改为JMP
,表示程序执行到这别管那么多,前面管他判断账号密码是否正确直接跳到调用显示输入正确信息的函数地址执行即可! -
接着保存程序
反汇编窗口右键–copy to executable–all modification–copy all(表示保存可执行文件的所有的修改,即将内存中此程序的所有数据保留一份)。再反汇编窗口右键–save file。另存为一个新的可执行文件–已经被修改的程序
-
上述过程就是爆破:不管你程序代码写的逻辑再严密,只要我找到了关键的跳转,把不跳的改为跳,反之亦然,就可以爆破你的程序!
4.逆向过程演示
-
将creakme.exe拖入OD中,在command窗口输入
bp MessageBoxA
回车,在MessageBoxA函数处设置断点,点击B
在breakpoint窗口查看断点是否设置成功 -
接着点击F9或者上面一排的小三角符号run program运行程序,接着输入账号和密码(字母)点击ok,输入错误则会触发MessageBoxA函数,但是由于设置了断点,则程序在执行MessageBoxA函数前会停下。下图中即为MessageBoxA函数的起始地址
-
接着根据函数执行前的栈顶中的数据即为此函数执行完后的返回地址,所以在栈顶右键选择Follow in Disassembler,追踪返回地址
-
接着看到messageBoxA函数上面部分即为显示输入错误信息的函数,也就是此函数中调用了MessageBoxA函数,用弹窗的方式显示了密码输入错误信息。所以我们现在要看是谁调用了输入错误信息的函数,继而在附近也可以找到满足什么条件可以调用输入正确信息的函数。
疑问:我们的最终目的不就是弹输入正确的弹框嘛,那你看上面图中,显示输入错误信息函数的上面那个函数不就是显示输入正确信息的函数嘛,为啥不能直接把断点定到正确的函数开头,直接查看输入正确信息的函数在哪里被调用,一改不就可以了?那是因为如果你在正确函数开始处设置断点表示程序如果执行时触发了调用了正确函数的条件跳转到了此正确函数的起始地址才能在断点处停下!但是我们不知道正确的账号密码就没法让程序执行这个输入正确信息函数,那么程序就不会停下,而是一直执行到底,所以断点一定要设置在程序会执行到的地方,如果程序都执行不到你设置断点的地方,那么断点设置的就没意义!
-
那么我们就要在此函数开头按f2设置断点,并且在breakpoint窗口将MessageBoxA函数开始处的断点取消,CTRL+F2重新加载程序,再f9运行程序,查看栈顶中的值。(因为此时设置的断点位置为MessageBoxA函数开始处,显示错误信息函数已经执行了,栈顶已经变化)
-
此时函数执行前的栈顶中的值即为此函数执行完后的返回地址。右键栈顶,选择follow in disassembler,追踪地址
-
追踪过去,返回地址的上面一条指令即为调用了显示输入错误信息的函数的起始地址,再看到上面cmp可能表示比较判断输入的账号或密码与正确的账号或密码是否一致,接着je是jcc的一种,决定它跳不跳到后面的地址还是继续向下执行的是标志寄存器Z,z为0则不跳,继续向下执行;如果为1则跳转到后面的地址,推测应该为显示输入正确的有关指令
-
此时可以在je这条指令处设置断点,让程序执行到je指令前先停下来。别忘了将原来的断点先取消,然后重新加载程序到内存中,再f9重新执行
-
此时将标志寄存器z从0改为1,再继续执行程序,可以发现程序已经弹输入正确的弹框了,表名je后面的地址就是输入正确信息有关指令。但是注意此时只是在内存中修改了z,下一次程序再加载到内存中,还是会回复原样
-
所以我们干脆直接将je这条指令改为jmp 到后面的地址,不要让程序还判断一下是否跳转,而是直接不管什么条件,直接强行跳转到输入正确的相关指令地址处。
-
反汇编窗口处右键–copy to executable–all modifications–copy all,保存所有的对程序的修改
-
再右键save file,另存为一个新的修改过的程序
-
那么当运行修改过后的程序时,随便输入账号密码都会弹输入正确的弹窗
二、作业
1.作业要求
-
上述程序中,如果输入数字型的账号和密码,会先弹输入错误信息的窗口,再弹输入正确信息的窗口,现在需要逆向修改程序,让程序在输入数字型的不管对错的账号和密码都可以只弹输入正确的窗口
2.逆向过程演示
-
我们知道只要是弹框就调用了MessageBoxA函数,但是我们这次需要找到在输入错误的数字信息时弹的框,所以我们同样先设置断点在MessageBoxA函数起始位置,然后执行程序,输入数字型账号和密码,触发第一次弹错误信息的框的条件
-
此时函数就会停到第一次调用的输入错误信息的MessageBoxA函数起始地址,此时栈顶中存放的就是此函数调用完后的返回地址,所以我们追踪过去,看看这个MessageBoxA函数在哪里被调用的
-
发现MessageBoxA函数就是在这个函数中被调用,所以我们要看看是谁在哪调用的这个输入错误信息的函数,即什么条件下会触发调用此函数,只要我们找到后不让它调用这个函数,让它直接调用第二个输入正确信息的函数即可
-
于是我们在0x004013AD处即此函数起始位置设置断点,并将最开始在MessageBoxA设置的断点取消,重新将程序载入内存CTRL+F2,并且再次输入数字型账号密码将程序停在此函数的起始位置
-
此时栈顶中的值即为显示输入错误数字型账号密码信息的函数调用完后的返回地址,所以右键栈顶指针追踪地址到返回地址
-
追踪过去后,往上找则可以看到call指令,这条指令就是调用了上面的输入错误数字型信息的函数,那么再往上找有一个cmp指令,可能就是判断数字型账号密码是否正确,然后下面又出现了一个JE,则标志寄存器z为0则不跳转,继续执行下面的指令,则就会执行到call输入错误函数指令,那么就调用了输入错误数字型信息的函数。那么现在只要==把JE指令改为跳转到调用输入正确函数的call指令的地址==(不能直接跳转到输入正确函数的首地址,因为那样是一个不完整的操作,不会像正常的调用一个函数一样,会先把返回地址存储到栈顶等),则输入任意数字型账号密码无论对错,都不会判断,而是直接强制跳转到
call 显示输入正确函数的地址
的指令,接着call指令会调用显示输入正确信息的函数,就可以达到我们想要的效果:输入任意的数字型账号密码,直接弹出输入正确的窗口 -
由于上面已经找到了显示输入正确信息的函数起始地址为0x0040134D,则将断定设置在这里,重新运行一遍程序,此时为了让程序能执行到断点处停下,则需要输入字母型账号密码(因为第一次creakme中已经将此程序破解了,只要输入任意字母性账号密码就会弹输入正确的窗口,所以我们要用此条件调用显示输入正确信息的函数,于是到达断点处程序就停下)。此时栈顶中存的就是此正确函数执行完后的返回地址,所以追踪到那
-
往上找可以看到call指令,这条call指令就会调用输入正确的函数,于是只要将刚才的JE后面的地址改为这个call指令的地址0x0040124C,则可以实现输入任意的数字型账号密码都只会弹一个输入正确的窗口
-
于是将je指令改为jmp 0x0040124C
-
接着保存全部修改,并且另存为一个新的程序,输入任意的数字型账号密码,则此时只会弹一个输入正确的窗口