前言:
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1791705-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 思路收集
1.下载后通过IDA反编译看看main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int v4; // [esp+5Ch] [ebp-70h]
char *v5; // [esp+60h] [ebp-6Ch]
char v6[27]; // [esp+6Ch] [ebp-60h] BYREF
char v7; // [esp+87h] [ebp-45h]
char *v8; // [esp+88h] [ebp-44h]
char *v9; // [esp+8Ch] [ebp-40h]
char *v10; // [esp+90h] [ebp-3Ch]
char v11[12]; // [esp+98h] [ebp-34h] BYREF
char v12[24]; // [esp+A4h] [ebp-28h] BYREF
int v13; // [esp+C8h] [ebp-4h]
sub_402930(v12);
v13 = 0;
sub_401530(&unk_4DDAF8, "please input flag");
sub_4039B0(sub_402310);
sub_401500(&unk_4DDA80, v12);
if ( sub_405DE0(v12) == 24 )
{
sub_402A20(v11);
LOBYTE(v13) = 1;
sub_402570(v11);
v10 = v12;
v9 = (char *)sub_405270(v12);
v8 = (char *)sub_4052B0(v12);
while ( v9 != v8 )
{
v7 = *v9;
sub_403B70(v7);
++v9;
}
qmemcpy(v6, "rxusoCqxw{yqK`{KZqag{r`i", 24);
sub_402590(v6);
v5 = (char *)sub_405290(v11);
v4 = sub_4052E0(v11);
while ( v5 != (char *)v4 )
{
if ( !(unsigned __int8)sub_403BB0(*v5) )
{
sub_401530(&unk_4DDAF8, "error");
sub_4039B0(sub_402310);
LOBYTE(v13) = 0;
sub_4034E0(v11);
v13 = -1;
sub_403450(v12);
return 0;
}
++v5;
}
sub_401530(&unk_4DDAF8, "good job");
sub_4039B0(sub_402310);
LOBYTE(v13) = 0;
sub_4034E0(v11);
v13 = -1;
sub_403450(v12);
result = 0;
}
else
{
sub_401530(&unk_4DDAF8, "not enought");
sub_4039B0(sub_402310);
v13 = -1;
sub_403450(v12);
result = 0;
}
return result;
}
2.通过观察看到第一个外层判断,像24这种可以猜测是长度,根据经验可知一般在验证密码时,会先判断非空,防止空指针异常报错,其次判断长度是否正确(这样可以避免对错误的密码进行大量的运算,提高运行速度),最后才是处理输入的密码,通过一系列运算与真实的密码进行比较。
if ( sub_405DE0(v12) == 24 )
3.第二次个判断在这里,可以看到有“error”字符串,说明这里也是一个关键,这个if如果成立,就代表密码错了。
if ( !(unsigned __int8)sub_403BB0(*v5) )
4.通过上述分析找到了关键判断的位置,那么就下来就要看这两个if比较的参数来自哪里?代表什么?接下来就需要结合动态调试进行判断了。
0x2 调试分析
1.在ida中找到第一个if对应的汇编代码和地址,直接OD中打断点运行过去,刚刚我们猜测这是长度,试试输入24个a看看。
00406824 . 83F8 18 cmp eax,0x18
00406827 . 74 42 je short happyCTF.0040686B
00406829 . 68 10234000 push happyCTF.std::endl<char,std::char_t>
0040682E . 68 D0F34A00 push happyCTF.004AF3D0 ; not enought
2.可以看到此时寄存器eax的值就是0x18,也就是24,那么通过第一个if可以知道密码长度了。
3.第二个if是与v5和v4相关,那么我们单步走,记录值的变化,这里这段对应ida里面第一个while循环代码段,通过单步可以知道捣鼓半天就是逐个取我们输入的字符,进004068C2这个call里面干了一些奇怪的事。
004068A1 > /8B45 C0 mov eax,dword ptr ss:[ebp-0x40]
004068A4 . |83C0 01 add eax,0x1
004068A7 . |8945 C0 mov dword ptr ss:[ebp-0x40],eax
004068AA > |8B45 C0 mov eax,dword ptr ss:[ebp-0x40]
004068AD . |3B45 BC cmp eax,dword ptr ss:[ebp-0x44]
004068B0 . |74 17 je short happyCTF.004068C9
004068B2 . |8B45 C0 mov eax,dword ptr ss:[ebp-0x40]
004068B5 . |8A08 mov cl,byte ptr ds:[eax]
004068B7 . |884D BB mov byte ptr ss:[ebp-0x45],cl
004068BA . |0FB645 BB movzx eax,byte ptr ss:[ebp-0x45]
004068BE . |50 push eax
004068BF . |8D4D C8 lea ecx,dword ptr ss:[ebp-0x38]
004068C2 . |E8 A9D2FFFF call happyCTF.<lambda_1b3a4e77a09e1a7ed4>
004068C7 .^\EB D8 jmp short happyCTF.004068A1
对应IDA反汇编出来的这一块:
while ( v9 != v8 )
{
v7 = *v9;
sub_403B70(v7);
++v9;
}
4.接下来看一下那个函数,a2就是我们输入的字符,可以看到做了一个异或0x14的操作,然后里面又调用了一些函数,看了半天看不懂,那就还是用OD进这个call看一下,。
int __thiscall sub_403B70(void *this, char a2)
{
char v3[65]; // [esp+Fh] [ebp-45h] BYREF
void *v4; // [esp+50h] [ebp-4h]
v4 = this;
v3[0] = a2 ^ 0x14;
sub_406170(v3);
return ++dword_4DD8F8;
}
00403B7C |. 0FB645 08 movzx eax,byte ptr ss:[ebp+0x8] ; 取我输入的字符a
00403B80 |. 83F0 14 xor eax,0x14 ; 异或0x14
00403B83 |. 8845 BB mov byte ptr ss:[ebp-0x45],al ; 将异或后的值存起来
5.那就结果论,打上断点重复走,可以看到不管哪个位置的a都是异或0x14得到u,可以猜测主要逻辑就是异或0x14,其他的可能是无关函数,继续往下,到第二个if那里,可以看到外面大层就是while循环,然后这里取加密后的字符u调用一个可疑函数,调用完之后就是判断跳转了,正好对应第二个if那里。
00406971 . 8D4D 9C lea ecx,dword ptr ss:[ebp-0x64] ; 加密后的第一个字符u
00406974 . E8 37D2FFFF call happyCTF.<lambda_7686c8adb828765130ce>
00406979 . 0FB6C8 movzx ecx,al
0040697C . 85C9 test ecx,ecx ; 根据ecx是否为0进行跳转,值来自于al寄存器
0040697E . 75 4B jnz short happyCTF.004069CB
00406980 . 68 10234000 push happyCTF.std::endl<char,std::char_tra>
00406985 . 68 DCF34A00 push happyCTF.004AF3DC ; error
6.进来00406974这个call可以判断出实际上整个程序就是拿我输入的字符串异或0x14,然后和固定字符串“rxusoCqxw{yqK`{KZqag{r`i”进行比较,相等就是正确的flag。
00403BC7 |. 0FB60411 movzx eax,byte ptr ds:[ecx+edx] ; r
00403BCB |. 0FB64D 08 movzx ecx,byte ptr ss:[ebp+0x8] ; u
00403BCF |. 3BC1 cmp eax,ecx ; 判断是否相等
00403BD1 |. 74 04 je short happyCTF.00403BD7
00403BD3 |. 32C0 xor al,al
00403BD5 |. EB 0F jmp short happyCTF.00403BE6 ; 如果不相等执行这个jmp,eax为1
00403BD7 |> A1 FCD84D00 mov eax,dword ptr ds:[indextocalealue_size>
00403BDC |. 83C0 01 add eax,0x1
00403BDF |. A3 FCD84D00 mov dword ptr ds:[indextocalealue_sizeopen>
00403BE4 |. B0 01 mov al,0x1
0x3 flag计算
1.经过前面的分析我们已经理清了整个程序的逻辑,接下来复制关键部分,直接进行解密,整理后的c代码如下。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char pass[24] = "rxusoCqxw{yqK`{KZqag{r`i";
char flag[24];
for (int i = 0; i < 24; i++)
{
flag[i]=pass[i]^0x14;
printf("%c",flag[i]);
}
system("pause");
return 1;
}
2.运行得到flag:
3.验证flag,正确。
0x4 总结
1.一步步反向推理,排除干扰函数,得到处理flag比较运算的函数。
2.看了别人的wp才知道这个题提供了pdb文件的,可以ida打开程序的时候加载,这样很容易区分,比如标注std的就是库函数,很长很陌生的函数就是自定义的,可以更快排除干扰函数。