0.上节课回顾
CreateWindowExA= > CreateWindowExW
- 32位api最终走向64位api上吗 不一定
- 花指令 就是断线 垃圾指令 跳转 -字节数 - 开始 有个块 切块
- Hex-Rays f5将汇编变成c语言代码
- 内存下断点 到 用户代码段
1.第八课作业
1.1 硬件断点
内存下了硬件断点 访问断点 那么别人要这段在程序里面的字符的时候 就会cpu卡住线程,我们想如何会用这段字符串?
-
messagebox函数
A. 算法内部的"字符逐一比对"
程序可能不是直接调用 MessageBox。它可能在执行类似你之前看到的 sub_4196A0 (strcmp) 算法。
- 现象: CPU 会停在类似
cmp al, [edx]这样的指令上。 - 意义: 这说明程序正在逐个字符检查你输入的注册码是否和这个错误提示字符串有关联。
B. 动态解密 (Self-Modifying Code)
有些程序为了防止你搜索字符串,会把 "注册失败" 加密存储。
- 现象: 你的硬件断点可能会在程序刚启动或者点击按钮时触发,停在一段循环里。
- 意义: 这说明程序正在解密这段字符。这通常离真正的校验逻辑还有一段距离。
C. 资源加载 (FindResource / LoadString)
Windows 程序有时把字符串存在"资源段"里。
- 现象: 触发断点的地方可能在系统的
User32.dll或Kernel32.dll内部。 - 意义: 你需要按 Alt + F9 执行到用户代码,才能看到是哪个 Call 请求加载了这个字符串。
如果是函数调用这段字符串那么进入函数之前应该是
push xxx[这段字符串 作为参数]
push xxx[这段字符串 作为参数]
call xxx 进入函数
【这时候堆栈就存下了"注册失败" 字符串 等下回到那里 这就明显可以找到调用信息框函数的关键跳/关键call】
完了我断不下来了???
取出垃圾指令
字符串直接搜索到
00401348 . 68 5C254400 push KeyGenMe.0044255C ; \n请输入注册码:
004013C0 > \68 88254400 push KeyGenMe.00442588 ; \n\n注册成功\n\n
od找可能的算法call
0040134D . E8 EE020000 call KeyGenMe.00401640
0040136A . E8 61020000 call KeyGenMe.004015D0
f5只会解析 sub_4196A0 这里代码为什么
c
int __cdecl sub_4196A0(_BYTE *a1, _BYTE *a2)
{
int v2; // edx
_BYTE *v3; // ecx
unsigned int v4; // eax
bool v5; // cf
unsigned int v6; // eax
__int16 v8; // ax
v2 = (int)a1;
v3 = a2;
if ( !((unsigned __int8)a1 & 3) )
{
LABEL_2:
while ( 1 )
{
v4 = *(_DWORD *)v2;
v5 = (unsigned __int8)*(_DWORD *)v2 < *v3;
if ( (unsigned __int8)*(_DWORD *)v2 != *v3 )
break;
if ( !(_BYTE)v4 )
return 0;
v5 = BYTE1(v4) < v3[1];
if ( BYTE1(v4) != v3[1] )
break;
if ( !BYTE1(v4) )
return 0;
v6 = v4 >> 16;
v5 = (unsigned __int8)v6 < v3[2];
if ( (_BYTE)v6 != v3[2] )
break;
if ( !(_BYTE)v6 )
return 0;
v5 = BYTE1(v6) < v3[3];
if ( BYTE1(v6) != v3[3] )
break;
v3 += 4;
v2 += 4;
if ( !BYTE1(v6) )
return 0;
}
return -v5 | 1;
}
if ( (unsigned __int8)a1 & 1 )
{
v2 = (int)(a1 + 1);
v5 = *a1 < *a2;
if ( *a1 != *a2 )
return -v5 | 1;
v3 = a2 + 1;
if ( !*a1 )
return 0;
if ( !(v2 & 2) )
goto LABEL_2;
}
v8 = *(_WORD *)v2;
v2 += 2;
v5 = (unsigned __int8)v8 < *v3;
if ( (_BYTE)v8 != *v3 )
return -v5 | 1;
if ( !(_BYTE)v8 )
return 0;
v5 = HIBYTE(v8) < v3[1];
if ( HIBYTE(v8) == v3[1] )
{
if ( HIBYTE(v8) )
{
v3 += 2;
goto LABEL_2;
}
return 0;
}
return -v5 | 1;
}
1.2 配置ida 显示中文字符串
-dCULTURE=all

1.3 更改函数名
通过看到这些字符串提示 显然sub_401560就算printf 重命名 然后等会好解析

x查看引用【谁call到】该函数地方

花指令无法解析
1.4 od取出花指令
中间都是垃圾指令 全部都是永远不会执行 因为上面的je让垃圾指令不会执行
要在花指令在的代码区 数据跟随 才能查到75 00 e8 垃圾指令 永远不会执行随便改 别错位


通过ctrl + b 对于 75 00 e8 的内存【花指令特征】设置为909090 因为 垃圾指令不会运行 干扰错位

让我难受的是他有memset 但是我没有memset解析???

2.代码段内存断点 执行到用户代码
2.1原理
当某块内存被访问时候被断下来;在系统API断点后对着代码段下内存断点,调试器是直接告诉 Windows 操作系统:"把这一整块内存页标记为'禁止访问'(或禁止执行) " CPU 试图跳到这个球场的任何一个角落只要碰到了这个区域,操作系统就会立刻报错(触发异常),调试器捕获这个异常,把程序暂停下来。
详细解释:
这样断点其实将代码段所有指令全部断下来了 在系统API这里断下来后继续向后执行 由于用户代码段全部被断下所以返回到了系统api被执行后的用户代码区而不是401000
- 内存写入断点
你下 "内存写入断点":你会看到解压算法正在往这里面填数据。时候 触碰这段的雷区
- 内存访问断点
当解压代码(在 UPX1)干完活后,它会执行一个长跳转(JMP)跳进 UPX0 去执行真正的程序原始入口点(OEP)。就在它跳进 UPX0 的第一瞬间,你的内存断点就会被触发!
2.2应用
看看某个壳子用了什么api【内存断点雷区】
看完之后返回到用户代码段【内存断点雷区】

使用了kernal32 的 loadLibraryA API

用户代码段 下断点 等下访问 用户代码区直接异常


3.结构体 数组
3.1 vs配置
f5插件


sdl







3.2 分析student
中...(img-kyTdDvXC-1770045448794)]
外链图片转存中...(img-SRcaxcxO-1770045448795)
外链图片转存中...(img-GoUCJ9b2-1770045448795)
外链图片转存中...(img-ViRBbVuv-1770045448795)
外链图片转存中...(img-8AggTdnk-1770045448795)
外链图片转存中...(img-ewCx50Iu-1770045448796)