.很开心,今天学习破解自己的第一个程序。
一个很小的c程序的例子,实现了一个简单的许可证检查。
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if(argc==2) {
printf("Checking License: %s\n", argv[1]);
if(strcmp(argv[1], "AAAA-Z10N-42-OK")==0) {
printf("Access Granted!\n");
} else {
printf("WRONG!\n");
}
} else {
printf("Usage: <key>\n");
}
return 0;
}
可以通过输入参数执行这个程序,我们的目标是破解此程序,以使我们绕过此密匙验证。
想要查看二进制汇编代码,我们可以使用一个名为gdb(GNU)的调试器。终端输入’'gdb”和要打开的文件路径:
gdb基本命令
gdb基本命令1:
backtrace(或bt): 查看各级函数调用及参数
finish: 连续运行到当前函数返回为止,然后停下来等待命令
frame(或f)帧编号: 选择栈帧
info(或i) locals: 查看当前栈帧局部变量的值
list(或l): 列出源代码,接着上次的位置往下列,每次默认列出10行
list行号:列出从第几行开始的源代码
list函数名:列出某个函数的源代码
next(或n):执行下一行语句
print(或p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
quit(或q):退出gdb调试环境
set var:修改变量的值
start:开始执行程序,停在main函数第一行语句前面等待命令
step(或s):执行下一行语句,如果有函数调用则进入到函数中
gdb基本命令2:
break(或b)行号:在某一行设置断点
break函数名:在某个函数开头设置断点
break…if…:设置条件断点
continue(或c):从当前位置开始连续运行程序
delete breakpoints 断点号:删除断点
display 变量名:跟踪查看某个变量,每次停下来都显示它的值
disable breakpoints 断点号:禁用某个断点
enable 断点号:启用断点
info(或i) breakpoints:查看当前设置了哪些断点
run(或r):从头开始连续运行程序
undisplay 跟踪显示号:取消跟踪显示
gdb基本命令3:
watch:设置观察点
info(或i) watchpoints:查看当前设置了哪些观察点
x:从某个位置开始打印存储单元的内容,全部当成字节来看,而不是分哪个字节属于哪个变量
disassemble:反汇编当前函数或者指定的函数,如果disassemble命令后面跟函数名或地址则反汇编指定的函数
si:执行一条指令
info registers:显示所有寄存器的当前值,在gdb中表示寄存器名时前面要加个$
print $(寄存器):打印指定寄存器的值
x/20 $rsp:显示内存中从当前栈顶开始的20个32位数。
下面回到开始的程序,设置为intel语法展示全部的汇编代码:
从一开始顺序的读这段代码是毫无意义的,这里的主要功能显然是要call其它函数做字符串的比较(strcmp),因此只需要绘制粗略的控制流程心里图就可以了。
开始的时候会和2进行比较
然后紧接着是jne的跳转
5cc cmp==2
5d0 jne 623
5ea printf
602 strcmp; (compares strings return 0 if the same)
607 test eax, eax
609 jne 617
(zeroflag=0 'string different'; ) if zf=1-->610 call puts
617
61c puts
623
628 call puts
现在,我们通过在gdb中逐步执行它:
在main的开头设置一个断点(break *main)
可以看到RIP(指令指针)指向main中的第一个地址。
使用’si’来执行一条指令,然后来到新的地址,可以看到指令指针的值被修改了。
现在逐步完成并按照上边控制图中的地址进行操作,使用’ni’进行单步执行,(重复的命令,只需要再次按下enter键就可以)
可以判断628 call puts那里调用:printdf "Usage: " information
再次运行程序,使用随机许可证密钥:
来到另外一处call的地方,这里的’printf’是检测许可证密钥的信息。
继续执行,来到分支部分ni,ni,ni,ni…
609 puts输出 “WRONG!”
设置断点再次执行,在执行’test eax,eax’之前停止
这里设置eax值为0xe,这表示’strcmp’是正确的并返回,zf=1,执行610 call puts:
再次ni继续执行
Access Granted!