最近这几天一直在看一本电子书,看雪的0day安全:软件漏洞分析技术
内容灵感来自:第一篇,第一章节最后一节1.4小实验
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"
// 验证输入的密码是否正确
int verify_password(char* password) {
int authenticated;
authenticated = strcmp(password, PASSWORD);
return authenticated;
}
int main() {
int valid_flag = 0;
char password[1024];
while (1) {
printf("please input password: ");
scanf_s("%s", password);
valid_flag = verify_password(password);
if (valid_flag) {
printf("incorrect password!\n\n");
}
else {
printf("Congratulation! You have passed the verification!\n");
break;
}
}
return 0;
}
这是书中给到的源码
编译前要关闭vs的内存地址随机化
环境:vs 2022 release 32位程序
IDA反编译后伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // eax
char password[1024]; // [esp+0h] [ebp-400h] BYREF
printf("please input password: ");
scanf_s("%s", password);
v3 = strcmp(password, "1234567");
if ( v3 )
v3 = v3 < 0 ? -1 : 1;
if ( v3 )
{
do
{
printf("incorrect password!\n\n");
printf("please input password: ");
scanf_s("%s", password);
v4 = strcmp(password, "1234567");
if ( v4 )
v4 = v4 < 0 ? -1 : 1;
}
while ( v4 );
}
printf("Congratulation! You have passed the verification!\n");
return 0;
}
看到这里进行了两次判断,第一次输入v3进行判断,第二次输入v4进行判断
下面介绍一下伪代码中的迷惑点
在v3和v4的strcmp函数下面会有这样的代码
if ( v3 ) v3 = v3 < 0 ? -1 : 1;
这个其实就是strcmp函数的返回值,可以不用管它
strcmp函数介绍 功能:从左至右逐个对字符串 s1 和 s2 中的字符按 ASCII 值大小进行比较 ,直到出现不同字符或遇 '\0' 为止。 返回值: 若 s1 等于 s2 ,返回 0。 若 s1 小于 s2 ,返回负数。 若 s1 大于 s2 ,返回正数。
伪代码的逻辑大致如下:
-
输入password,通过strcmp函数来判断。然后将返回的结果返还给v3
-
if 判断v3的值,如果不是等于0(那就是密码输入错误了)就会执行if内的内容,如果等于零直接执行 printf("Congratulation! You have passed the verification!\n"); 那很不错了(๑•̀ㅂ•́)و✧
-
假如输入错误,进入到if体内,先给我们打印出密码输入错误,然后重新提示我们输入密码。
-
这次重新输入password,使用strcmp来比较,将返回结果赋给v4。这里有意思的是,最后没有if判断v4是否等于零,直接用do while来判断v4是否等于零。如果v4等于零,那么直接跳出循环并打印Congratulation! You have passed the verification!,如果v4不等于0(负数也包含在内)那么就会一直在do while循环中徘徊直到我们输入的password正确。
流程图分析1
由于流程图太多太长,这里就不一一展示了,只挑重点的说
loc_4010E7 这个就是我们第一个if,也就是判断我们v3的if。
这里再介绍两个汇编指令:
test:指令用于对两个操作数进行按位逻辑与运算,但不保存运算结果,仅根据运算结果设置标志寄存器 jz条件跳转指令,全称为 “Jump if Zero”,即当零标志位(ZF)为 1 时,程序会跳转到指定的目标地址执行;若零标志位(ZF)为 0,则顺序执行下一条指令。
这个是来判断我们v3的值是否为0,如果我们v3的值为0的话就会执行jz跳转指令,直接跳到密码正确那里。
test eax,eax 来判断eax的值是否为0,如果为0则执行jz跳转,不为零则向右侧执行
破解点1
在test eax,eax指令这里按下空格
记住这个内存地址 4010E7
然后我们将程序拖入到x32dbg中
Ctrl+G 打开查询框,输入我们内存地址4010E7,然后跳转到那里
将test eax,eax的值修改为xor eax,eax 这样一来ZF标志位肯定为1了
xor eax, eax 是将 eax 寄存器的值与自身进行按位异或运算。由于 eax 中的每一位都与自身相同,根据按位异或运算规则,每一位的运算结果都为 0。 由于 xor eax, eax 的运算结果为 0,根据 ZF 的设置规则,零标志位(ZF)会被置为 1。
所以下面的那条jz指令就会自动变成了je指令
je指令 je 是 “Jump if Equal” 的缩写,也就是当零标志位(ZF)的值为 1 时,程序就会跳转到指定的目标地址继续执行;若零标志位(ZF)为 0,程序则会按顺序执行下一条指令。
这样,我们就算破解完成了
然后导出看下成果
正确密码是1234567,但是这里输入1234就提示正确 (๑•̀ㅂ•́)و✧
流程图分析2
这个跟上一个类似,但是这个是do while来判断是否为0的,上面那个是if
jnz指令 jnz 是 “Jump if Not Zero” 的缩写,即当零标志位(ZF)的值为 0 时,程序会跳转到指定的目标地址继续执行;若零标志位(ZF)为 1,则程序会顺序执行下一条指令。
这里可能有点乱,我来梳理一下下
jnz指令,是0标志位ZF为0的时候 (也就是v4不是0的时候)发生跳转 jz指令,是0标志位ZF为1的时候(也就是v3是0的时候)发生跳转
我这里发懒了,直接nop掉了
原样
后者
直接给你跳转nop掉,让你啥也不是,然后保存补丁
依旧可以,这里为什么第一遍会错呢?
这个程序至少有两次判断,第一个判断是通过判断v3,第二个是判断v4的。我们改的是v4那个while判断所以第一遍输入v3的内容时候自然会提示密码错误,但是第二遍输入的时候就提示密码正确了
正确密码依旧是:1234567