学破解第141天,《攻防世界reverse练习区IgniteMe》学习
## 学破解第141天,《攻防世界reverse练习区IgniteMe》学习前言:
  从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。
  2017年12月16日,通过19元注册码注册论坛账号,开始做伸手党,潜水一年多,上来就是找软件。(拿论坛高大上的软件出去装X)
  2018年8月10日,报名了华中科技大学网络教育本科(计算机科学与技术专业)2018级秋季。(开始提升学历)
  2019年6月17日,不愿再做小菜鸟一枚,开始零基础学习破解。(感谢小糊涂虫大哥在我刚开始学习脱壳时,录制视频解答我的问题)
  2020年7月7日,感谢H大对我的鼓励,拥有了第一篇获得优秀的文章。(接下来希望学习逆向,逆天改命)
  2021年8月11日,华科学位英语2次不过,仅取得了毕业证书,学业提升失败,开始琢磨考注册类和职称类证书,谋求涨薪
  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:(https://www.52pojie.cn/thread-1278021-1-1.html)
立帖为证!--------记录学习的点点滴滴
### 0x1 下载文件
  1.下载CM,是一个exe文件。
  2.exeInfo工具查壳:
!(https://z3.ax1x.com/2021/08/31/hdVKWd.png)
  3.使用studyPE查看程序信息:
!(https://z3.ax1x.com/2021/08/31/hdVUYQ.png)
  4.可能有的坛友不知道为什么我都要重复这个步骤,这里说明下:
第一步看文件类型,exe是win平台,elf是Linux平台,能在win平台跑我就可以动态调试了。
第二步看目标程序是否有壳,以决定是脱壳还是带壳打补丁。
第三步主要是看程序基址是否固定以便固定基址方便调试和打补丁,另自带有密码学探测工具,这里暂时用不到。
  5.通过上述信息,我知道了这个程序大概率是vc++6.0编写的32位程序,无壳,固定基址,未使用标准加密算法。
### 0x2 OD调试分析
  1.既然是exe文件还是先爆破,将程序载入OD,搜索字符串,进度条拉到最上方:
!(https://z3.ax1x.com/2021/08/31/hdZ5CQ.png)
  2.回车,在几处错误提示的字符串处F2下断点:
```
00401069|.83F8 04 cmp eax,0x4
0040106C|.77 25 ja short fac4d129.00401093
0040106E|>68 70364000 push fac4d129.00403670
00401073|.68 60B14300 push fac4d129.0043B160 ;Sorry, keep trying!
```
```
004010F5|.3BD1 |cmp edx,ecx
004010F7|.74 25 |je short fac4d129.0040111E
004010F9|.68 70364000 |push fac4d129.00403670
004010FE|.68 40B14300 |push fac4d129.0043B140 ;Sorry, keep trying!
```
```
004010F5|.3BD1 |cmp edx,ecx
004010F7|.74 25 |je short fac4d129.0040111E
004010F9|.68 70364000 |push fac4d129.00403670
004010FE|.68 40B14300 |push fac4d129.0043B140 ;Sorry, keep trying!
```
```
00401124|.83FA 7D cmp edx,0x7D
00401127|.74 22 je short fac4d129.0040114B
00401129|.68 70364000 push fac4d129.00403670
0040112E|.68 40B14300 push fac4d129.0043B140 ;Sorry, keep trying!
```
```
0040115C|.85C0 test eax,eax ;kernel32.BaseThreadInitThunk
0040115E|.75 22 jnz short fac4d129.00401182
00401160|.68 70364000 push fac4d129.00403670
00401165|.68 40B14300 push fac4d129.0043B140 ;Sorry, keep trying!
```
  3.将程序跑起来,输入123456,断在0040106C 这里,单步往下走看看:
00401069|.83F8 04 cmp eax,0x4
这里必须跳过去,否则失败,eax的值6,可以推测就是我输入的123456的长度
004010F5|.3BD1 |cmp edx,ecx
edx是0x31,可以猜到是输入的第一个字符1对应的ASCII值,ecx是0x45 E,判断两个值是否相等,不相等就失败,这里我们在寄存器窗口点Z,改变标志,让它跳过这个失败,开始下一次循环。
(https://imgtu.com/i/hdmqtU)
第一轮循环
ECX 00000045E
EDX 000000311
第二轮循环
ECX 00000049I
EDX 000000322
第三轮循环
ECX 00000053S
EDX 000000333
第四轮循环
ECX 0000007B{
EDX 000000344
00401124|.83FA 7D cmp edx,0x7D
这里目前还没看出来什么意思,改跳转继续往正确的流程走
0040114F|.E8 6C000000 call fac4d129.004011C0
00401154|.83C4 04 add esp,0x4
00401157|.25 FF000000 and eax,0xFF
0040115C|.85C0 test eax,eax
0040115E|.75 22 jnz short fac4d129.00401182
再往下只有Sorry, keep trying! 和Congratulations! 两条路。
  4.显然0040114F这里调用的call就是关键函数了,在回到第一个断点往上翻:
```
00401058|.83F8 1E cmp eax,0x1E
0040105B|.73 11 jnb short fac4d129.0040106E
0040105D|.8D55 80 lea edx,
00401060|.52 push edx
00401061|.E8 6AE20100 call fac4d129.0041F2D0
00401066|.83C4 04 add esp,0x4
00401069|.83F8 04 cmp eax,0x4
0040106C|.77 25 ja short fac4d129.00401093
0040106E|>68 70364000 push fac4d129.00403670
00401073|.68 60B14300 push fac4d129.0043B160 ;Sorry, keep trying!
```
这里判断了0x1E和0x4,jnb不小于则跳转,ja大于则跳转,根据前面推测,eax是我输入字符串的长度,所以我输入的字符串长度应该是4-30位,而开头的4位必须是“EIS{”
  5.到这里把0040115E这个jnz让它跳过就成功爆破了。
!(https://z3.ax1x.com/2021/08/31/hd8y0H.png)
  6.当然了,爆破对CTF题来说意义不大,接下来还是分析一下算法吧。
### 0x3 IDA代码分析
  1.将程序用32位IDA打开,找到main函数,F5反编译一下:
```
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
size_t i; //
char v5; // BYREF
char Str; // BYREF
sub_402B30(&unk_446360, "Give me your flag:");
sub_4013F0(sub_403670);
sub_401440(Str, 127);
if ( strlen(Str) < 0x1E && strlen(Str) > 4 )
{
strcpy(v5, "EIS{");
for ( i = 0; i < strlen(v5); ++i )
{
if ( Str != v5 )
goto LABEL_7;
}
if ( Str != 125 )
{
LABEL_7:
sub_402B30(&unk_446360, "Sorry, keep trying! ");
sub_4013F0(sub_403670);
return 0;
}
if ( (unsigned __int8)sub_4011C0(Str) )
sub_402B30(&unk_446360, "Congratulations! ");
else
sub_402B30(&unk_446360, "Sorry, keep trying! ");
sub_4013F0(sub_403670);
result = 0;
}
else
{
sub_402B30(&unk_446360, "Sorry, keep trying!");
sub_4013F0(sub_403670);
result = 0;
}
return result;
}
```
  2.前面和我在OD中分析的一样,先判断我输入的字符串长度是否满足要求,再看是否为EIS{开头,再调用sub_4011C0函数验证
```
if ( (unsigned __int8)sub_4011C0(Str) )
sub_402B30(&unk_446360, "Congratulations! ");
```
通过OD查找调用过程,发现从004019C3这个地址调用的004017F0函数,向上翻找到段首00401890。
  3.看一看sub_4011C0函数反编译后的代码:
```
bool __cdecl sub_4011C0(char *Str)
{
size_t v2; // eax
int v3; //
char Str2; // BYREF
int v5; //
int v6; //
size_t i; //
char v8; // BYREF
if ( strlen(Str) <= 4 )
return 0;
i = 4;
v6 = 0;
while ( i < strlen(Str) - 1 )
v8 = Str;
v8 = 0;
v5 = 0;
v3 = 0;
memset(Str2, 0, sizeof(Str2));
for ( i = 0; ; ++i )
{
v2 = strlen(v8);
if ( i >= v2 )
break;
if ( v8 >= 97 && v8 <= 122 )
{
v8 -= 32;
v3 = 1;
}
if ( !v3 && v8 >= 65 && v8 <= 90 )
v8 += 32;
Str2 = byte_4420B0 ^ sub_4013C0(v8);
v3 = 0;
}
return strcmp("GONDPHyGjPEKruv{{pj]X@rF", Str2) == 0;
}
```
while ( i < strlen(Str) - 1 )
v8 = Str;
v8 = 0;
将str字符串给到char数组v8,末尾补0;
memset(Str2, 0, sizeof(Str2));
初始化str2字符串
接下来就是一段关键的字符串处理了,最后比较str2和GONDPHyGjPEKruv{{pj]X@rF是否相等,这段for循环可以像前面一样不用理解它做了什么,直接等式变换,大于变小于等于,加变减,异或不用变,直接用它的算法些脚本得到flag,这里我们学习一下阅读代码,如果能读懂一会写脚本就不会知其然不知其所以然。
  4.继续看看for循环这段代码:
v2 = strlen(v8);
if ( i >= v2 )
break;
这里就是判断是否超过我输入的字符串长度了,超过了直接跳出循环,失败
if ( v8 >= 97 && v8 <= 122 )
{
v8 -= 32;
v3 = 1;
}
这里急判断如果我输入的是小写字母就变成大写字母,这里v3的作用目前还看不出来。
if ( !v3 && v8 >= 65 && v8 <= 90 )
v8 += 32;
如果是大写字母就变成小写字母,使用完后v3又重置为0,v3是大小写判断,例如前面判断小写了,则这里不再判断是否为大写。
Str2 = byte_4420B0 ^ sub_4013C0(v8);
这段代码就这句有用,这里又调用了一个函数
int __cdecl sub_4013C0(int a1)
{
return (a1 ^ 0x55) + 72;
}
所以str2 = byte_4420B0^((v8 ^ 0x55) + 72),循环执行。
byte_4420B0是一个char数组,存储的内容为:
!(https://z3.ax1x.com/2021/08/31/hdU3X6.png)
为了便于一会容易理解,将str2 = byte_4420B0^((v8 ^ 0x55) + 72)拆分:
v8 = v8 ^ 0x55
v8 = v8 + 72
str2 = v8 ^ byte_4420B0
倒过来就是:
v8 = str2 ^ byte_4420B0
v8 = v8 -72
v8 = v8 ^ 0x55
  5.整理后的代码:
```
package ctf0;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
char temp[] = { 0x0D, 0x13, 0x17, 0x11, 0x2, 0x1, 0x20, 0x1D, 0x0C, 0x2, 0x19, 0x2F, 0x17, 0x2B, 0x24, 0x1F,
0x1E, 0x16, 0x9, 0xF, 0x15, 0x27, 0x13, 0x26, 0x0A, 0x2F, 0x1E, 0x1A, 0x2D, 0x0C, 0x22, 0x4 };
StringBuffer str = new StringBuffer("GONDPHyGjPEKruv{{pj]X@rF");
StringBuffer flag = new StringBuffer("GONDPHyGjPEKruv{{pj]X@rF");
boolean on = true;//大写
for (int i = 0; i < str.length(); i++) {
flag.setCharAt(i, (char) (str.charAt(i)^temp));
flag.setCharAt(i, (char) (flag.charAt(i)-72));
flag.setCharAt(i, (char) (flag.charAt(i)^0x55));
if(flag.charAt(i)>=97&&flag.charAt(i)<=122)
{
on = false;//小写
flag.setCharAt(i,(char) (flag.charAt(i)-32));
}
if(on==true&&flag.charAt(i)>=65&&flag.charAt(i)<=90)
{
flag.setCharAt(i,(char) (flag.charAt(i)+32));
}
on = true;
}
System.out.println(flag);
}
}
```
运行后:wadx_tdgk_aihc_ihkn_pjlm
  6.提交flag,发现一直不对???什么情况!!!一脸懵X,回去复盘一下代码,看看忽略了什么,还记得前面OD分析的时候,必须EIS{开头吗?再去看看:
if ( strlen(Str) < 0x1E && strlen(Str) > 4 )
这是长度要求
strcpy(v5, "EIS{");
for ( i = 0; i < strlen(v5); ++i )
{
if ( Str != v5 )
goto LABEL_7;
}
这是判断flag开头前四位是否是EIS{
if ( Str != 125 )
这是判断flag第29位是}
i = 4;
v6 = 0;
while ( i < strlen(Str) - 1 )
v8 = Str;
再看,i是从4开始的,赋值时去掉了前四位,strlen(Str) - 1,赋值时去掉了最后一位。
所以flag应该是EIS{wadx_tdgk_aihc_ihkn_pjlm}
  7.再次提交flag,验证通过:
!(https://z3.ax1x.com/2021/08/31/hdDl1H.png)
### 0x4总结
  1.分析的时候应该逐步推进,不能只盯着关键函数,注意循环时变量的起始位置,字符串拷贝不一定全部复制过去了。
  2.flag不对时小菜鸟差点哭了,明明分析的没问题,为什么会这样,最后冷静下来,耐心复盘就找到了原因。
  3.编写脚本时用了不熟悉的java,查找函数耗费了一些时间(不能总是只会一门c语言吧,适当了解别的语言)
  4.这是道1分题,主要是还是大意了,将脚本跑起来以为就成功了,却忘了前面为什么判断EIS{了。
  5.尝试读懂代码,更有利于编写脚本。
### 0x5参考资料
  1.(https://www.matools.com/api/java8)
  **PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!** 楼主我觉得你不如多放点精力琢磨考注册类和职称类证书,破解这类东西作为兴趣玩玩可以 感谢楼主分享,我也刚入坑,不知道怎么学习哇,现在有前辈的经验,我们后辈会少走很多弯路啊 加油鸭 楼主 点赞加油 正想学软件和破解,挺有用的 祝楼主涨薪100美元 破解APP不,去一下广告
向楼主学习 码住,试一下 加油 加油 一起学习