【reverse】新160个CrackMe之154-cpp_crackme1——MFC+纯算法逆向
本帖最后由 hans7 于 2022-9-3 21:05 编辑### 依赖
IDA版本为7.7。
### PETools查看概况
32位程序,入口点`Section: [.text], EP: 0x00001DB9`故无壳。
### 正文
**作者:(https://blog.csdn.net/hans774882968)以及(https://juejin.cn/user/1464964842528888)以及(https://www.52pojie.cn/home.php?mod=space&uid=1906177)**
本文juejin:https://juejin.cn/post/7139136489585115173/
本文csdn:https://blog.csdn.net/hans774882968/article/details/126682443
本文52pojie:https://www.52pojie.cn/thread-1683826-1-1.html
#### 如何定位关键函数
1. 因为函数很少,所以一个一个点开来看,即可定位到关键函数`sub_401660`。
2. 打开x64dbg,正常地触发一次输入序列号错误,**点击暂停键**,查看调用堆栈,即可定位。
#### 分析
`sub_401660`:
```c
int sub_401660()
{
int iii; // ebx
int *v2; // edi
int v3; // eax
int i; // ecx
int v5; // ecx
char *v6; // edi
int v7; // edx
int v8; // esi
char *v9; // eax
int v10; // ecx
int v11; // ecx
int *v12; // edi
int WindowTextA; // eax
char v14; // BYREF
CHAR Caption; // BYREF
CHAR Text; // BYREF
char String; // BYREF
__int16 v18; //
char Buf1; // BYREF
__int16 v20; //
char v21; //
char Buf2; // BYREF
__int16 v23; //
char v24; //
int v25; //
int v26; //
CHAR Str1; // BYREF
char Source; // BYREF
char v29; //
memset(String, 0, sizeof(String));
v18 = 0;
memset(Buf1, 0, sizeof(Buf1));
v20 = 0;
memset(Buf2, 0, sizeof(Buf2));
v23 = 0;
memset(v14, 0, sizeof(v14));
memset(Source, 0, 30);
if ( GetWindowTextA(dword_40974C, String, 30) >= 1 )
{
iii = 0;
v2 = &dword_407030;
do
{
if ( iii >= lstrlenA(String) )
Buf1 = (iii + (*v2 ^ 0x5A)) % 57 + 65;
else
Buf1 = (iii + 1) ^ String;
++v2;
++iii;
}
while ( (int)v2 < (int)&unk_4070A8 );
v21 = 0;
_swab(Buf1, Buf2, 30);
v24 = 0;
v3 = 0;
for ( i = 0; i < 30; ++i )
{
v29 = Buf2;
LOBYTE(v3) = v29;
v3 = __ROR4__(v3, 1);
v29 = v3;
Buf2 = v3;
}
v5 = 0;
v6 = v14;
do
{
v7 = Buf2;
v8 = Buf1;
v6 += 4;
++v5;
*((_DWORD *)v6 - 1) = v7 * v8 + (v7 ^ v8);
}
while ( v5 < 30 );
v9 = v14;
v10 = 15;
do
{
*(_DWORD *)v9 += *((_DWORD *)v9 + 15);
v9 += 4;
--v10;
}
while ( v10 );
v11 = 0;
v12 = (int *)v14;
do
{
v25 = *v12;
LOBYTE(v26) = v25;
++v12;
Source = (unsigned __int8)v25 % 9 + 48;
}
while ( v11 < 15 );
memset(&Source, 0, 25);
Source = toupper(dword_407030 ^ 0x63);
Source = toupper(dword_407034 ^ 0x63);
Source = toupper(dword_407038 ^ 0x63);
Source = '-';
strncat(&Source, Source, 0xFu);
qmemcpy(&Source, "-2413", 5);
WindowTextA = GetWindowTextA(dword_409750, Str1, 25);
if ( WindowTextA >= 1 )
{
if ( WindowTextA >= 24
&& !strncmp(Str1, &Source, 0x14u)
&& (Str1 ^ 0x63) == 0x53
&& (Str1 ^ 0x63) == 0x52
&& (Str1 ^ 0x63) == 0x57
&& (Str1 ^ 0x63) == 0x52 )
{
sub_401B20(byte_4071CC, 12);
qmemcpy(Caption, byte_409754, 0xFFu);
sub_401B20(byte_4071FC, 10);
qmemcpy(Text, byte_409754, 0xFFu);
return MessageBoxA(hWnd, Caption, Text, 0x40u);
}
else
{
sub_401B20(byte_407190, 15);
qmemcpy(Caption, byte_409754, 0xFFu);
sub_401B20(byte_407178, 6);
qmemcpy(Text, byte_409754, 0xFFu);
return MessageBoxA(hWnd, Caption, Text, 0x10u);
}
}
else
{
sub_401B20(byte_407118, 24);
qmemcpy(Caption, byte_409754, 0xFFu);
sub_401B20(byte_407178, 6);
qmemcpy(Text, byte_409754, 0xFFu);
return MessageBoxA(hWnd, Caption, Text, 0x10u);
}
}
else
{
sub_401B20(byte_4070C0, 22);
qmemcpy(Text, byte_409754, 0xFFu);
sub_401B20(byte_407178, 6);
qmemcpy(Caption, byte_409754, 0xFFu);
return MessageBoxA(hWnd, Text, Caption, 0x10u);
}
}
```
这里有两个全局变量,`dword_40974C`和`dword_409750`分别对应你的Name和你输入的序列号。
##### 常量串隐藏
为什么不能通过常量串定位关键函数?因为做了常量串隐藏。看到`sub_401B20`就是一个字符串解密操作,我们写个idapython脚本看看各个常量串:
```python
import idc
def get_dec_str(enc):
ans = ''
for i in range(0, len(enc), 4):
ans += chr(enc ^ 0x63)
return ans
a = [
, , ,
, ,
]
mp = {}
for addr, c in a:
enc_s = get_bytes(addr, c)
dec_s = get_dec_str(enc_s)
mp = dec_s
print(mp)# {'0x4070c0': 'You must enter a name!', '0x407118': 'You must enter a serial!', '0x407178': 'Error!', '0x407190': 'Invalid serial!', '0x4071cc': 'Good serial!', '0x4071fc': 'Well Done!'}
```
接下来我们从下到上分析每一个小模块。
判定:
```c
WindowTextA = GetWindowTextA(dword_409750, Str1, 25);
if ( WindowTextA >= 24
&& !strncmp(Str1, &Source, 0x14u) // Str1是你输入的序列号
&& (Str1 ^ 0x63) == 0x53
&& (Str1 ^ 0x63) == 0x52
&& (Str1 ^ 0x63) == 0x57
&& (Str1 ^ 0x63) == 0x52 )
```
需要关注`Source + 16`字符串怎么来:
```c
v12 = (int *)v14;
do
{
v25 = *v12;
LOBYTE(v26) = v25;
++v12;
Source = (unsigned __int8)v25 % 9 + 48;
}
while ( j < 15 );
memset(&Source, 0, 25);
// 期望的序列号的前4个字符:'ULT-'
Source = toupper(dword_407030 ^ 0x63);
Source = toupper(dword_407034 ^ 0x63);
Source = toupper(dword_407038 ^ 0x63);
Source = '-';
strncat(&Source, Source, 0xFu); // 所以Source[:15]有用
qmemcpy(&Source, "-2413", 5); // 迷惑你的,但用到了"-2413"
```
所以重点关注对`v14`所在内存空间的修改:
```c
v6 = v14;
do
{
v7 = Buf2; // 注意这里是有符号扩展
v8 = Buf1;
v6 += 4;
++ii;
*((_DWORD *)v6 - 1) = v7 * v8 + (v7 ^ v8); // v6+4-1*4
}
while ( ii < 30 );
// 相当于 for i in range(15): v14 += v14
v9 = v14;
v10 = 15;
do
{
*(_DWORD *)v9 += *((_DWORD *)v9 + 15);
v9 += 4;
--v10;
}
while ( v10 );
```
值得注意的是,`v7`是int,`Buf2`是char,cpp里把char赋值给int的隐式类型转换是“**有符号扩展**”,即把`Buf2`解释为8位有符号整数。对应的汇编指令为:`movsx`。验证demo:
```cpp
#include <windows.h>
#include <stdio.h>
int main() {
char c1, c2, c3;
c1 = 0xfe;
c2 = 0xfc;
c3 = 0xfa;
int v1 = c1, v2 = c2, v3 = c3;
printf ("%d %d %d\n", v1, v2, v3); // -2 -4 -6
return 0;
}
```
接下来看`Buf1`和`Buf2`怎么求得。
求`Buf2`:
```c
_swab(Buf1, Buf2, 30);
v3 = 0;
for ( i = 0; i < 30; ++i )
{
v29 = Buf2;
LOBYTE(v3) = v29;
v3 = __ROR4__(v3, 1);
v29 = v3;
Buf2 = v3;
}
```
1. `_swab`即相邻字符串为一组,交换位置。
2. 易错点:`v3 = __ROR4__(v3, 1)`不能理解成右移1位,因为`v3`是int类型,在**操作24次后**将不等价于右移1位。
`_swab`:
```c
void __cdecl _swab(char *Buf1, char *Buf2, int SizeInBytes)
{
unsigned int v4; // esi
char v6; // dl
char *v7; // ecx
if ( SizeInBytes > 1 )
{
v4 = (unsigned int)SizeInBytes >> 1;
do
{
v6 = *Buf1;
*Buf2 = Buf1;
Buf1 += 2;
v7 = Buf2 + 1;
*v7 = v6;
Buf2 = v7 + 1;
--v4;
// Buf2 = Buf1, Buf2 = Buf1, Buf1 += 2, Buf2 += 2
}
while ( v4 );
}
}
```
求`Buf1`:
```c
iii = 0;
v2 = &dword_407030; // GetWindowTextA(dword_40974C, String, 30),所以String就是你的Name
do
{
if ( iii >= lstrlenA(String) )
Buf1 = (iii + (*v2 ^ 0x5A)) % 57 + 65;
else
Buf1 = (iii + 1) ^ String;
++v2;
++iii;
}
while ( (int)v2 < (int)&unk_4070A8 );
```
因此,我们要做的,是把生成序列号的算法复现出来,算法参数:你输入的`Name`。
### 踩坑总结
1. 把char赋值给int的隐式类型转换是“**有符号扩展**”。
2. 不要想当然地把`__ROR4__`简化为右移一位。python实现时不要怕麻烦。
### 代码
这里我们有两个看上去不错的选择:
1. 用python来复现。
2. 用cpp来复现,并用IDA的`defs.h`来减少代码的改动。`defs.h`在IDA安装目录下可搜索到。
我把两种方法都做了,法二的心智负担明显更小。
#### 法一
```python
def get_buf1(inp_name):
dword_407030 = [
0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x17, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x16, 0x00,
0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00,
0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00
]
buf1 = []
for i in range(30):
buf1.append(
ord(inp_name) ^ (i + 1) if i < len(inp_name) else
((i + (dword_407030 ^ 0x5A)) % 57 + 65)
)
return buf1
def get_buf2(buf1):
buf2 = []
for i in range(0, 30, 2):
buf2.append(buf1)
buf2.append(buf1)
def ror4_1(v): return ((v & 1) << 31) | (v >> 1)
v3 = 0
for i, v in enumerate(buf2):
v3 = (v3 & 0xffffff00) | v
v3 = ror4_1(v3)
buf2 = v3 & 0xff
return buf2
def solve(inp_name: str):
ans16_19 = ''.join([chr(i ^ 0x63).upper()
for i in ]) + '-'
print(ans16_19)# ULT-
ans_tail = '-2413'
serial_tail = ''.join(])
print(serial_tail)# 0141
buf1 = get_buf1(inp_name)
buf2 = get_buf2(buf1)
v14 = []
def as_signed8(v): return v if v < 0x7f else (v - 0x100)
for x, y in zip(buf1, buf2):
x = as_signed8(x)
y = as_signed8(y)
v14.append(y * x + (y ^ x))
for i in range(15):
v14 += v14
ans_main = ''
for i in range(15):
ans_main += chr((v14 & 0xff) % 9 + 48)
ans = ans16_19 + ans_main + '-' + serial_tail
return ans
inp_name = "hans"
ans = solve(inp_name)
print(inp_name, ans, len(ans))
inp_name = "acmer"
ans = solve(inp_name)
print(inp_name, ans, len(ans))
inp_name = "Hans774882968"
ans = solve(inp_name)
print(inp_name, ans, len(ans))
```
#### 法二
```cpp
#include <bits/stdc++.h>
#include <windows.h>
#include "defs.h"
using namespace std;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
read (x);
read (r...);
}
string solve (string inpName) {
int v3; // eax
int v5; // ecx
char *v6; // edi
int v7; // edx
int v8; // esi
char *v9; // eax
int *v12; // edi
char v14; // BYREF
char Buf1; // BYREF
char v21; //
char Buf2; // BYREF
char v24; //
int v26; //
char v29; //
int dword_407030 = {
0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x17, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x16, 0x00,
0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00,
0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00
};
memset (Buf1, 0, sizeof (Buf1) );
memset (Buf2, 0, sizeof (Buf2) );
memset (v14, 0, sizeof (v14) );
re_ (i, 0, 30) {
if (i >= inpName.size() )
Buf1 = (i + (dword_407030 ^ 0x5A) ) % 57 + 65;
else
Buf1 = (i + 1) ^ inpName;
}
v21 = 0;
for (int i = 0; i < 30; i += 2) {
Buf2 = Buf1;
Buf2 = Buf1;
}
v24 = 0;
v3 = 0;
re_ (i, 0, 30) {
v29 = Buf2;
LOBYTE (v3) = v29;
v3 = __ROR4__ (v3, 1);
v29 = v3;
Buf2 = v3;
}
v5 = 0;
v6 = v14;
do {
v7 = Buf2;
v8 = Buf1;
v6 += 4;
++v5;
* ( (_DWORD *) v6 - 1) = v7 * v8 + (v7 ^ v8);
} while ( v5 < 30 );
v9 = v14;
re_ (_, 0, 15) {
* (_DWORD *) v9 += * ( (_DWORD *) v9 + 15);
v9 += 4;
}
v12 = (int *) v14;
string ansMain;
re_ (_, 0, 15) {
int v25 = *v12;
++v12;
ansMain += (unsigned __int8) v25 % 9 + 48;
}
return "ULT-" + ansMain + "-0141";
}
int main() {
string inpName, ans;
inpName = "hans acmer";
ans = solve (inpName);
dbg (inpName, ans, ans.size() );
inpName = "Hans774882968";
ans = solve (inpName);
dbg (inpName, ans, ans.size() );
inpName = "scuctf";
ans = solve (inpName);
dbg (inpName, ans, ans.size() );
return 0;
}
```
### 参考资料
1. movsx命令:https://blog.csdn.net/cswangbin/article/details/3955395
2. https://www.bilibili.com/video/BV16d4y1d72A 教程写的很详细,一看就是下了苦功夫的{:1_921:} 萌新学习中,感谢大佬 感谢大佬。。。。。。。。。。。 教程很详细,谢谢大佬! 教程很详细,谢谢 Null666yyds 发表于 2022-9-3 22:47
教程写的很详细,一看就是下了苦功夫的
写这个确实很辛苦{:1_936:} 感谢楼主分享,谢谢分享学习知识
页:
[1]