学破解第132天,《攻防世界reverse练习区Replace》学习
前言:
从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。
2017年12月16日,通过19元注册码注册论坛账号,开始做伸手党,潜水一年多,上来就是找软件。(拿论坛高大上的软件出去装X)
2018年8月某一天,报名了华中科技大学网络教育本科(计算机科学与技术专业)2018级秋季。(开始提升学历)
2019年6月17日,不愿再做小菜鸟一枚,开始零基础学习破解。(感谢小糊涂虫大哥在我刚开始学习脱壳时,录制视频解答我的问题)
2020年7月7日,感谢H大对我的鼓励,拥有了第一篇获得优秀的文章。(接下来希望学习逆向,逆天改命)
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1278021-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1下载文件
1.是一个压缩包,下载解压出来后是一个exe程序。
2.看到exe小菜鸟就放心,论坛的xp虚拟机太强大了,看小菜鸟如何用OD+lordPE+IRI脱UPX壳。
3.小菜鸟将程序放进exe程序,发现居然打不开?小菜鸟告诉自己不慌,在win7真机环境下下载一个吾爱OD,运行程序居然自己退出了。
4.小菜鸟只好直接打开这个程序,观察观察,发现就是要求输入正确的key,否则退出:
0x2新工具X32dbg的学习
1.经过尝试,小菜鸟发现x32dbg工具能正常打开exe程序,虽然是一个陌生的工具,小菜鸟还是照着吾爱OD的操作,单步一次
013D7D20 | 60 | pushad |
013D7D21 | BE 00703D01 | mov esi,replace.13D7000 |
013D7D26 | 8DBE 00A0FFFF | lea edi,dword ptr ds:[esi-6000] |
013D7D2C | 57 | push edi |
013D7D2D | 83CD FF | or ebp,FFFFFFFF |
013D7D30 | EB 10 | jmp replace.13D7D42 |
2.右键-》数据窗口中跟随,然而x32dbg工具中我找了半天,也没有找到数据窗口,最左下角是内存窗口,于是小菜鸟就跟随到内存1窗口,在内存1窗口中设置硬件断点,运行,取消硬件断点,单步几次到达OEP,vc++程序的入口特征就不用说了吧,就它了
013D13B0 | E8 DC020000 | call replace.13D1691 |
013D13B5 | E9 91FEFFFF | jmp replace.13D124B |
013D13BA | 55 | push ebp |
3.接下来怎么办呢?lordpe只能显示60个进程,看不到这个,没办法,小菜鸟又去下载了PEtool,都是英文,小菜鸟琢磨不透,最后只能试试x32dbg自带的scylla工具,还好,界面比较简单,能看懂,还能自己识别我找到的OEP,挺方便的,自动查找IAT,获取输入表,先dump,然后Fix Dump修复,程序脱壳成功,然后运行程序,程序异常了???
4.这个问题打了小菜鸟一个措手不及,对x32dbg小菜鸟也不熟,完全不知道问题到底是出在哪了?UPX脱壳步骤应该也是没有问题的啊?
5.小菜鸟没办法,百度c0000005异常也没找到什么有用的知识,说的都是兼容性什么的。异常偏移0000169f这个值引起了我的注意,想去x32dbg重新跑一下程序看看,这一看,小菜鸟发现程序的每次入口点都不一样:
01357D20 | 60 | pushad |
00DB7D20 | 60 | pushad |
013A7D20 | 60 | pushad |
00B77D20 | 60 | pushad |
6.小菜鸟这下就知道是动态基址的问题的了,于是就去论坛搜索相关帖子,找到了用studype固定基址的办法(见文章末尾参考资料),固定基址后,程序正常运行。
0x3使用IDA分析程序
1.程序已经被脱壳了,那就好办了,丢到IDA里面走起来,main函数已经被自动识别出来了,直接F5
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Buf; // [esp+4h] [ebp-2Ch]
char Dst; // [esp+5h] [ebp-2Bh]
Buf = 0;
memset(&Dst, 0, 0x27u);
printf("Welcome The System\nPlease Input Key:");
gets_s(&Buf, 0x28u);
if ( strlen(&Buf) - 35 <= 2 )
{
if ( sub_13D1090(&Buf) == 1 )
printf("Well Done!\n");
else
printf("Your Wrong!\n");
}
return 0;
}
2.跟着代码看一看,首先从键盘接收长度为40(0x28u)的字符串,然后计算字符串的长度减去35要小于等于2才会进入内层if,然后满足sub_13D1090(&Buf) == 1才是正确。
3.进入sub_13D1090这个函数一探究竟,代码如下:
signed int __fastcall sub_13D1090(int a1, int a2)
{
int v2; // ebx
int v4; // edx
char v5; // al
int v6; // esi
int v7; // edi
char v8; // al
int v9; // eax
char v10; // cl
int v11; // eax
int v12; // ecx
v2 = a1;
if ( a2 != 35 )
return -1;
v4 = 0;
while ( 1 )
{
v5 = *(_BYTE *)(v4 + v2);
v6 = (v5 >> 4) % 16;
v7 = (16 * v5 >> 4) % 16;
v8 = byte_13D2150[2 * v4];
if ( v8 < 48 || v8 > 57 )
v9 = v8 - 87;
else
v9 = v8 - 48;
v10 = byte_13D2151[2 * v4];
v11 = 16 * v9;
if ( v10 < 48 || v10 > 57 )
v12 = v10 - 87;
else
v12 = v10 - 48;
if ( (unsigned __int8)byte_13D21A0[16 * v6 + v7] != ((v11 + v12) ^ 0x19) )
break;
if ( ++v4 >= 35 )
return 1;
}
return -1;
}
4.还是继续使用我们的逆向思维,倒着看,要使if等于1,也就是必须return 1,要想return 1,v4要大于等于34,然后上面一堆的if else,小菜鸟已经懵圈了。
0x4换x32dbg动态调试
1.首先,没得说,直接13D1090下断点,构造长度为35位的字符串12345678912345678912345678912345678,输入,运行:
013D1090 | 53 | push ebx | ebx:"12345678912345678912345678912345678"
013D1091 | 8BD9 | mov ebx,ecx | ebx:"12345678912345678912345678912345678", ecx:"12345678912345678912345678912345678"
013D1093 | 83FA 23 | cmp edx,23 | 23:'#'
013D1096 | 74 05 | je 1.13D109D | 如果字符串长度不为35(0x23)直接返回-1
2.看的小菜鸟头皮发麻,继续看,进入了while循环,根据前面的推导,必须return 1退出循环才行,那么v4显然就是控制循环退出的条件!再看看if ( v8 < 48 || v8 > 57 )这一句,我们判断是不是数字经常用的,如果是数字减去48,否则减去87,v8和v10是从两个数组中取数据。
既然如此,那么这两个数组的元素是什么呢?
直接x32dbg内存中就能看到:
byte ptr [edx*2+13D2150]=[013D2150 "2a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6"]=32 '2'
byte ptr [edx*2+13D2151]=[013D2151 "a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6"]=61 'a'
真的是看的清清楚楚,明明白白啊!
3.再看v5 = v4 + v2,v2是ebx,看看x32dbg,ebx就是我们输入的字符串12345678912345678912345678912345678,接着就看v6和v7是在干啥了,右移4位对16取余, v7先乘16在右移四位对16取余,按我的理解就是v6取v5的低4位,v7取v5的高4位?
4.接着也就是看最重要一句代码,跳向失败的条件:if ( (unsigned __int8)byte_13D21A0[16 * v6 + v7] != ((v11 + v12) ^ 0x19) ),每一次循环必须让这个等式成立。
13D21A0这个数组的值呢?
我姑且当二维数组来看,由于v7是对16取余,所以这个数组应该有16*16=256个元素,关键是这些元素的值是多少,真的是要人命啊,有没有技巧?
......
一小时后,小菜鸟通过16*0+0,一直计算到16*15+15,通过基址+偏移量终于得到了这256个元素,小菜鸟真是太傻了,百度一下就有(不知道这些大佬们怎么算的):
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
5.接下来就开始写代码爆破了:
// demo_ctf1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "stdlib.h"
int main()
{
int v4 = 0, v5 = 0, v6 = 0, v7 = 0, v8 = 0, v9 = 0, v10 = 0, v11 = 0, v12 = 0;
char arr1[] = "2a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6";
char arr2[] = "a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6";
char flag[36] = "";
char arr3[] = {
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};
while (1)//前面的代码直接照搬IDA反编译的结果
{
v8 = arr1[2 * v4];
if (v8 < 48 || v8 > 57)
v9 = v8 - 87;
else
v9 = v8 - 48;
v10 = arr2[2 * v4];
v11 = 16 * v9;
if (v10 < 48 || v10 > 57)
v12 = v10 - 87;
else
v12 = v10 - 48;
for (v5 = 32; v5 < 127; v5++)//代入一个个去试等式成立的条件
{
v6 = (v5 >> 4) % 16;
v7 = (16 * v5 >> 4) % 16;
if ((unsigned __int8)arr3[16 * v6 + v7] == ((v11 + v12) ^ 0x19))//找到正确的字符就加入flag
{
flag[v4] = v5;
break;
}
}
v4++;
if (v4 >= 35)
{
flag[v4] = 0;//flag字符串末尾补\0
break;
}
}
printf("%s\n", flag);
system("pause");
return 0;
}
0x5总结
1.x32dbg手脱UPX那里浪费了时间,就是个随机基址的问题。
2.这个程序的逻辑并不难,脱壳后放入IDA可以清晰的看到爆破条件,即使看不懂那个判断函数,也可以把代码copy出来爆破。
3.主要复习了一维数组和二维数组的识别,认识了一下x32dbg的界面和简单使用。
4.找最后一个二维数组时,没想到什么好办法,也不会脚本,纯体力活给它一个个找出来,从前到后花了6个小时才做出来。
5.逆向还是要耐心和技巧,逆向逆向就是倒着思考问题,由果寻因的过程。
0x6参考资料
1.[我的代码传记]upx脱壳重定位表修复](https://www.52pojie.cn/thread-1295193-1-1.html)
2.https://www.cnblogs.com/DirWang/p/12236700.html
PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!