学破解第78天,《攻防世界reverse新手练习区game分析》
前言:
一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程,随着破解学习的深入,越发的觉得自己没有基础,所以从第71天起,开始接触一门编程语言C++,开启新的学习之路。
立帖为证!--------记录学习的点点滴滴
为啥选择它呢?当然是因为楼主是个小菜鸟,这道题简单,就拿它分析分析了!
1.下载程序
这是一个exe文件,那我就放心,至少可以在window上执行,可以用OD。
2.观察程序
直接将程序运行起来,一堆英文,看不懂,右键复制,丢到有道翻译里面去。
有道翻译如下:
玩一个游戏
n是灯的序列号,m是灯的状态
如果第n盏灯的m是1,它是亮的,如果不是,它是灭的
起初所有的灯都是关着的
现在你可以输入n来改变它的状态
但是你要注意一件事,如果你改变第N盏灯的状态,第(N-1)个和第(N+1)个的状态也会改变
当所有的灯都亮着时,旗子就会出现
现在,输入n
输入n, n (1 - 8)
游戏规则清楚了,但是我显然智商没那么高,玩不来,那就只能老老实实对它使用吾爱的神器OD了。前灯亮,相邻的两个灯同时改变自身状态。
思考:我们能不能点一个亮一个,不改变相邻灯的状态呢?
3.使用OD调试
首先查壳,Microsoft Visual C++ ver.14 [Deb] / Visual Studio 2015 [ Debug:02 ] ,ep区段.text 应该是无壳的C++程序!
然后将程序丢进OD,停在了01417839 这里,直接右键搜索中文字符串,搜到了一大堆提示信息,我找到程序提示的游戏规则的最后一句input n,n(1-8),找到这一行回车进去,来到了:
0141F508 |. 68 7CB54C01 |push 9c91e9fd.014CB57C ; input n,n(1-8)\n
0141F50D |. E8 ACB2FFFF |call 9c91e9fd.0141A7BE
0141F512 |. 83C4 04 |add esp,0x4
0141F515 |. E8 FE9EFFFF |call 9c91e9fd.01419418
0141F51A |. 68 90B54C01 |push 9c91e9fd.014CB590 ; n=
一路F8单步往下跟,在0131F530 这一个call程序跑起来等待我们输入n,输入完之后继续单步向下走,然后就看到了CLS清屏命令,随后灯的状态就改变了。
暂时失去了思路
再去看看字符串有没有哪些可用的信息,我看到了这个字符串:done!!! the flag is ,大意就是说成功了,flag是
但是我们没有看到这句话出来,我猜测是不是把这句话显示出来就能看到flag呢?
我回车进去,定位到了这一段代码:
013EE940 /> \55 push ebp
013EE941 |. 8BEC mov ebp,esp
013EE943 |. 81EC 58010000 sub esp,0x158
013EE949 |. 53 push ebx
013EE94A |. 56 push esi
013EE94B |. 57 push edi
013EE94C |. 8DBD A8FEFFFF lea edi,[local.86]
013EE952 |. B9 56000000 mov ecx,0x56
013EE957 |. B8 CCCCCCCC mov eax,0xCCCCCCCC
013EE95C |. F3:AB rep stos dword ptr es:[edi]
013EE95E |. A1 04204C01 mov eax,dword ptr ds:[0x14C2004] ; 蛾Am
013EE963 |. 33C5 xor eax,ebp
013EE965 |. 8945 FC mov [local.1],eax
013EE968 |. 68 F0B04901 push 9c91e9fd.0149B0F0 ; done!!! the flag is
013EE96D |. E8 4CBEFFFF call 9c91e9fd.013EA7BE
我们现在就要一步一步向上回溯,找到跳转到这里的位置
点击段首,看信息窗口,CTRL+G向上找,依次找到:013EE940《-013E7AB4 《-013EF66C
我们向上翻翻,看到了CLS,我现在又有一个思路了:能不能在清屏后重新显示菜单的的时候让它调用这个call直接显示flag呢?
那我就F4到CLS这里,n随便输入一个5
013EF5B7 |> \68 BCB54901 |push 9c91e9fd.0149B5BC ; CLS
013EF5BC |. E8 F68BFFFF |call 9c91e9fd.013E81B7
013EF5C1 |. 83C4 04 |add esp,0x4
013EF5C4 |. E8 8B8AFFFF |call 9c91e9fd.013E8054
013EF5C9 |. B8 01000000 |mov eax,0x1
看这里,第一个call就是调用系统的清屏命令,第二个call回车进去看了下,就是打印菜单,那就说明灯的状态在CLS之前就已经改变了,看来前面是我错过那个了CALL,继续看,后面紧接着就是013EF66C这个call,肯定是被跳转过去了,继续单步,来到这里,看图,这个jnz不能让它跳!
然后后面代码和这一块类似,很容易猜测到是判断其它7个灯是不是亮的,我就不用去一个一个nop了,直接将第一个jnz改成jmp。
0023F5DB /E9 8C000000 jmp 9c91e9fd.0023F66C
复制到可执行文件,保存为1.exe,运行程序后,随便输入一个数字即可得到正确的falg,如图所示!
到这里,我们就成功的使用OD找到了flag!
4.使用IDA进行分析
将程序丢进IDA,在左侧的函数栏CTRL+F搜索main函数,F5反汇编一下,看到
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
main_0();
return result;
}
我们往下跟,看到它调用了main_0函数,点进去,来到了如图所示的地方!
前面都是些打印提示语函数,直接略过,看下面这段反汇编代码,我给它加了注释
while ( 1 )//死循环,让游戏不会结束
{
while ( 1 )
{
sub_45A7BE("input n,n(1-8)\n");
sub_459418();\\1.△ 2.○ 3.◇ 4.□ 5.☆ 6.▽ 7.( ̄▽ ̄)/ 8.(;°Д°) 0.restart
sub_45A7BE("n=");
sub_4596D4("%d", &v1);\\接收输入的数字
sub_45A7BE("\n");
if ( v1 >= 0 && v1 <= 8 )//判断输入的值是否在1-8的范围内,否则重新开始游戏
break;
sub_45A7BE("sorry,n error,try again\n");
}
if ( v1 )//v1也不能等于0
{
sub_4576D6(v1 - 1);//
}
else//否则将所有灯关掉,m全部设为0
{
for ( i = 0; i < 8; ++i )
{
if ( (unsigned int)i >= 9 )
j____report_rangecheckfailure();
byte_532E28[i] = 0;
}
}
j__system("CLS");
sub_458054();//改变灯的状态
if ( byte_532E28[0] == 1
&& byte_532E28[1] == 1
&& byte_532E28[2] == 1
&& byte_532E28[3] == 1
&& byte_532E28[4] == 1
&& byte_532E28[5] == 1
&& byte_532E28[6] == 1
&& byte_532E28[7] == 1 )
{
sub_457AB4();//如果灯全部亮,输出flag
}
}
加上注释后,是不是一目了然呢,我们看到上面的代码瞬间涌现出两种思路:
1.输入一个0,在OD中找到'00FDF59D C681 282E0B01>mov byte ptr ds:[ecx+0x10B2E28],0x0'这一行,然后改为0x1,我们保存出去,输入0,即可得到flag。
2.跟进sub_457AB4();这个函数,看flag是怎么算出来的。
5.flag算法还原与总结
前面已经通过IDA和OD分别获取到了flag,我们进去看一下sub_457AB4()函数的代码,如何运算出我们的flag的,F5反编译一下
核心算法在这:
for ( i = 0; i < 56; ++i )
{
*(&v2 + i) ^= *(&v59 + i);
*(&v2 + i) ^= 0x13u;
}
前面都是定义的一堆变量v59-v115为一段,v2-v58为一段,看for循环(&v2 + i) ^= (&v59 + i);,可以看做a[i]^b[i],然后a[i]^0x13u得到解密后的字符。
整理一下,最终改写成C语言代码如下:
int sub_45E940()
{
int i;
int arr1[]={18,64,98,5,2,4,6,3,6,48,49,65,32,12,48,65,31,78,62,32,49,32,1,57,96,3,21,9,4,62,3,5,4,1,2,3,44,65,78,32,16,97,54,16,44,52,32,64,89,45,32,65,15,34,18,16,0};
int arr2[]={123,32,18,98,119,108,65,41,124,80,125,38,124,111,74,49,83,108,94,108,84,6,96,83,44,121,104,110,32,95,117,101,99,123,127,119,96,48,107,71,92,29,81,107,90,85,64,12,43,76,86,13,114,1,117,126,0};
for(i = 0;i < 56; i++)
{
arr1[i] ^= arr2[i];
arr1[i] ^= 0x13u;
printf("%c",arr1[i]);
}
return 1;
}
int main(int argc, char* argv[])
{
printf("Hello World!\n");
sub_45E940();
getchar();
return 0;
}
总结:楼主是个小菜鸟,离了教程啥都不会!
PS:IDA不会用,这一部分的分析参考了其他大佬的分析!