学习环境:Windows 10 20H2 + Visual Studio 2019 + IDA
参考书籍:《C++反汇编与逆向分析技术揭秘》Chapter 5.1--5.3
看这种书籍上的介绍吧,总是想去动手练练,书上也没提供多少练习,这里啊,就开始自己写Crackme给自己练吧,当天学啥了,就写啥来逆
源程序
#include <stdio.h>
#include <string.h>
// 输入参数:ch5_carck1.exe username password
int main(int argc, char* argv[], char* envp) {
char szPass[0x20] = { 0 };
char szPassAdd[0x20] = { 0 };
if (strlen(argv[1]) < 5) {
printf("用户名过短");
return 0;
}
strcpy(szPass, argv[1]);
// 判断首字母是否是数字
if ('0' < argv[1][0] && argv[1][0] < '9') {
strcat(szPass, "_2021_");
sprintf(szPassAdd,"%d",argv[1][0] + argv[1][2] + 1);
strcat(szPass, szPassAdd);
}
else if (argv[1][0] = 'A') {
strcat(szPass, "_2021_");
sprintf(szPassAdd, "%d", argv[1][1] * argv[1][3]);
strcat(szPass, szPassAdd);
}
else {
strcat(szPass, "_2021_");
sprintf(szPassAdd, "%d", argv[1][4] / argv[1][1]);
strcat(szPass, szPassAdd);
}
//printf("正确密码:%s", szPass);
if (!strcmp(szPass, argv[2])) {
printf("密码正确");
}
else {
printf("密码错误");
}
return 0;
}
由于代码写错了,else if那里条件少写了个等号,所以编译出来这个条件分支就没了。。尴尬,将错就错吧,下次争取不犯错
逆向分析
这里就IDA静态分析,用VS2019编译好release版本,保留pdb文件,然后就开始吧
F5C代码和反汇编一起看,分析效率比较高
IDA还原C代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *input; // esi
char *input_1; // ecx
char input_nextchar; // al
char v6; // dl
char *szPass_t; // ecx
int nNum; // ecx
unsigned int szPassAdd_Length; // edx
char *szPass_t1; // edi
int v14; // ecx
const char *conclusion; // eax
char szPassAdd[32]; // [esp+4h] [ebp-44h] BYREF
char szPass[32]; // [esp+24h] [ebp-24h] BYREF
*(_OWORD *)szPass = 0i64;
input = (char *)argv[1];
*(_OWORD *)&szPass[16] = 0i64;
*(_OWORD *)szPassAdd = 0i64;
*(_OWORD *)&szPassAdd[16] = 0i64;
if ( strlen(input) >= 5 )
{
// strcpy
input_1 = input;
do
{
input_nextchar = *input_1++;
input_1[szPass - input - 1] = input_nextchar;
}
while ( input_nextchar );
v6 = *input;
szPass_t = &szPassAdd[31];
if ( (unsigned __int8)(*input - '1') > 7u )
{
*input = 'A';
while ( *++szPass_t ) // 将指针指向字符串最后
;
*(_DWORD *)szPass_t = *(_DWORD *)a202; // strcat(szPass,"_202")
strcpy(szPass_t + 4, "1_"); // strcat(szPass, "1_");
nNum = argv[1][1] * argv[1][3]; // nNum = argv[1][1] * argv[1][3]
}
else
{
while ( *++szPass_t )
;
*(_DWORD *)szPass_t = *(_DWORD *)a202;
strcpy(szPass_t + 4, "1_");
nNum = v6 + input[2] + 1; // nNum = input[0] + input[2] + 1
}
sprintf(szPassAdd, "%d", nNum); // szPassAdd = nNum
// strcat(szPass,szPassAdd)
szPassAdd_Length = strlen(szPassAdd) + 1;
szPass_t1 = &szPassAdd[31];
while ( *++szPass_t1 )
;
qmemcpy(szPass_t1, szPassAdd, szPassAdd_Length);
v14 = strcmp(szPass, argv[2]);
if ( v14 )
v14 = v14 < 0 ? -1 : 1;
conclusion = "密码错误";
if ( !v14 )
conclusion = "密码正确";
printf(conclusion);
}
else
{
printf("用户名过短");
}
return 0;
}
乱七八糟的,第一次用IDA F5功能看代码,我发现这里有些函数被优化到识别不出来
strlen识别出来了,strcat、strcpy反而识别不出来,不知道是啥问题
strcpy 函数
代码进入if分支之后的第一段:
input_1 = input;
do
{
input_nextchar = *input_1++;
input_1[szPass - input - 1] = input_nextchar;
}
while ( input_nextchar );
看他的汇编:
.text:004010D0 strcpy: ; CODE XREF: _main+5B↓j
.text:004010D0 mov al, [ecx] ; ecx里是argv[1], 这里是取第一个字母
.text:004010D2 lea ecx, [ecx+1] ; 取下一个字母的地址到ecx
.text:004010D5 mov [edx+ecx-1], al ; 将取出的字母放入szPass的下一位
.text:004010D9 test al, al ; 判断是否把字母全部取出
.text:004010DB jnz short strcpy ; ecx里是argv
看C看不明白,看汇编秒懂哈哈哈哈
strcat 函数
里面这一段代码出现了两遍
while ( *++szPass_t ) // 这里while循环将字符串指针指向字符串末尾
;
*(_DWORD *)szPass_t = *(_DWORD *)a202; // 将'_202'这4个字节的字符赋值给字符串末尾
strcpy(szPass_t + 4, "1_"); // 然后使用strcpy函数将'1_'给添加到字符串末尾+4个字节之后
这里看C勉强能看
代码还原
然后就是些零零碎碎的计算了,可以直接做代码还原了:
#include<stdio.h>
#include<string.h>
int main(int argc, char* argv[], char* envp) {
char szPass[32] = { 0 };
char szPassAdd[32] = { 0 };
int nNum = 0;
if (strlen(argv[1]) >= 5) {
strcpy_s(szPass,argv[1]);
if (argv[1][0] - '1' > 7) {
argv[1][0] = 'A';
strcat_s(szPass,"_2021_");
nNum = argv[1][1] * argv[1][3];
}
else {
strcat_s(szPass, "_2021_");
nNum = argv[1][0] + argv[1][2] + 1;
}
sprintf_s(szPassAdd,"%d",nNum);
strcat_s(szPass,szPassAdd);
if (!strcmp(szPass, argv[2])) {
printf("密码正确");
}
else {
printf("密码错误");
}
}
else {
printf("用户名过短");
}
}
不知道为什么,这个判断条件被优化成了这个怪怪的东西,但结果是对的