学破解第104天,《XPS to PDF v2.1工具》分析
前言:
一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程。随着破解学习的深入,楼主这个半吊子迷失了自我,日渐沉迷水贴装X,不能自拔。
==========申明:从第71天楼主开始水贴装X,帖子不再具有连续性,仅供参考,后续帖子为楼主YY专用贴!!!==========
立帖为证!--------记录学习的点点滴滴
0x1起源
今天上来论坛逛一逛,发现消息又是红色的,点开,果然又是什么有偿破解,有偿脱壳。我就是一菜鸟,我也想钱,但是知道自己几斤几两,干不了,也从来不理这些。
翻了一番,发现了两个值得回复的帖子:
第一个:
你好 我也是一个小白 看到你的励志与勇气 使我也鼓起勇气来尝试学习 破解 现在也在看ximo的视频现在卡在第四个视频上边了 OD用的是吾爱专用的那个版本 和ximo老师的貌似不是一个版本 然后发现添加的load PE 无法像你和老师那样 以及Import REC这个软件爱盘里也没找到 想问问如何去搞到呢
第二个:
XPS to PDF v2.1(http://www.adultpdf.com/products/xpstopdf/index.html) ,这个软件可以把XPS文件转换为PDF,我试着爆破,去除了注册窗口,但注册采用重启验证,转换文件后,有水印,后来发现水印是在DLL文件中,加了ASPROTECT壳,脱壳?
看过我的广播贴的人都知道,我也是从零基础小白,慢慢从脱壳,再到官方教程一步一步过来的,很感激小糊涂虫大哥在我刚开始学脱壳的时候解答我的疑问。看到这样的帖子就能想到当初的我,第一个坛友的问题,我就建议他用论坛XP虚拟机入门,集成了很多工具,然后把爱盘Import REC的下载地址复制给他了。
第二个问题,这位兄弟可算把我问住了,我只脱过exe的,dll怎么脱壳???接下来重点分析一下这个软件。
0x2下载安装软件
1.去官网下载软件:
2.安装软件,看一下有哪些文件,看一下程序窗口。
0x3 dll查壳与脱壳
1.查壳,xpstopdf.dll这个文件确实显示ASProtect V2.X DLL壳。
2.载入OD,使用Loaddll.exe加载,停在了
104F8001 > 60 pushad
104F8002 E8 03000000 call xpstopdf.104F800A
104F8007 - E9 EB045D45 jmp 55AC84F7
104F800C 55 push ebp
104F800D C3 retn
看到pushad就能想到esp定律,F8一次,看右侧窗口寄存器esp:0012F868,然后在底部命令行输入hr 0012F868下硬件访问断点,SHIFT+F9
00D10140 5C pop esp ; LoadDl_1.60001057
00D10141 03C3 add eax,ebx ; LoadDl_1.<ModuleEntryPoint>
00D10143 894424 1C mov dword ptr ss:[esp+0x1C],eax ; xpstopdf.10336074
00D10147 61 popad
00D10148 - FFE0 jmp eax ; xpstopdf.10336074
看到jmp eax,啥也不用说,F8一次,到达OEP了
10336074 55 push ebp
10336075 8BEC mov ebp,esp
10336077 53 push ebx ; LoadDl_1.<ModuleEntryPoint>
10336078 8B5D 08 mov ebx,dword ptr ss:[ebp+0x8] ; LoadDl_1.60000000
1033607B 56 push esi
1033607C 8B75 0C mov esi,dword ptr ss:[ebp+0xC]
1033607F 57 push edi
10336080 8B7D 10 mov edi,dword ptr ss:[ebp+0x10]
10336083 85F6 test esi,esi
3.此时,就要LordPE登场了,找到Loaddll,然后在下面找到对应的dll,完整转存出去,如下图所示操作。
4.接下来用Import REC修复,同样选择Loaddll程序,然后点击右侧的选取dll,输入OEP:6074,自动查找IAT,获取输入表,发现有两个无效函数,使用等级1修复下,不行。
5.既然不能自动查找IAT,那我就手动来一下,载入程序,直接在00403000(data段下断点),SHIFT+F9,发现了大批已经解密的函数,数据窗口上下翻
10344010 77EFEB2A gdi32.CreateFontIndirectA
10344014 77EF6BFA gdi32.DeleteObject
10344018 77EF5B70 gdi32.SelectObject
1034401C 77EFF170 gdi32.GetFontData
10344020 77EF9BB3 gdi32.GetTextCharset
10344024 00000000
10344028 7C92FE01 ntdll.RtlGetLastWin32Error
1034402C 2D6EBEA7
10344030 7C812FD9 kernel32.GetStdHandle
10344034 7C809BE7 kernel32.CloseHandle
10344038 7C80DE9E kernel32.DuplicateHandle
1034403C C6BA3C80
10344040 B9FDB276
10344044 7C801A28 kernel32.CreateFileA
10344048 7C861967 kernel32.GetTempFileNameA
1034404C 78C25C39
10344050 7C834EE1 kernel32.FindNextFileA
10344054 7C813879 kernel32.FindFirstFileA
10344058 658C7799
1034405C 7C81127A kernel32.GetVersion
10344060 114D6361
10344064 B7C27B4E
10344068 37AB20EA
1034406C 4F396723
10344070 C5B0DA6C
10344074 64B7C86A
10344078 7C80A874 kernel32.GetLocalTime
1034407C 7C809F91 kernel32.InitializeCriticalSection
10344080 D75862B5
10344084 00D37574
10344088 7C801D7B kernel32.LoadLibraryA
1034408C 7C9210E0 ntdll.RtlLeaveCriticalSection
10344090 43A4BFDC
10344094 7C812B7E kernel32.GetVersionExA
10344098 1DC0A88F
1034409C F4796686
103440A0 7C80A174 kernel32.WideCharToMultiByte
103440A4 CE1E9093
103440A8 056CF040
103440AC 58E14692
103440B0 7C80A3FE kernel32.CompareStringW
103440B4 7C80D117 kernel32.CompareStringA
103440B8 7C80BE56 kernel32.lstrlenA
103440BC 7C80934A kernel32.GetTickCount
103440C0 7C84495D kernel32.SetUnhandledExceptionFilter
103440C4 67918E31
103440C8 7C93135A ntdll.RtlDeleteCriticalSection
103440CC 3EE1D004
103440D0 48BC326E
103440D4 7C812FA8 kernel32.GetEnvironmentStringsW
103440D8 7C81CC93 kernel32.GetEnvironmentStringsA
103440DC 7C814B87 kernel32.FreeEnvironmentStringsW
103440E0 941C1F01
103440E4 7C921000 ntdll.RtlEnterCriticalSection
103440E8 2E0F27D6
103440EC F8482181
103440F0 FC1CD367
103440F4 62968FB4
103440F8 BA7C4140
103440FC 76736655
10344100 7C81CB12 kernel32.ExitProcess
10344104 63BDF8D3
10344108 7C80981A kernel32.InterlockedDecrement
1034410C 7C809806 kernel32.InterlockedIncrement
10344110 7C92FF0D ntdll.RtlFreeHeap
10344114 7C9300A4 ntdll.RtlAllocateHeap
10344118 64C03497
1034411C 7C801812 kernel32.ReadFile
10344120 627D0AAE
10344124 5D1723DF
10344128 7C939B80 ntdll.RtlReAllocateHeap
1034412C 7EF8AE4D
10344130 7C94ABA5 ntdll.RtlUnwind
10344134 55B468A1
10344138 8E72A59B
1034413C 7C812AA9 kernel32.RaiseException
10344140 AF088F91
10344144 85A8BCE5
10344148 7C809C65 kernel32.TlsSetValue
1034414C 7C812E3F kernel32.TlsAlloc
10344150 7C813777 kernel32.TlsFree
10344154 7C92FE10 ntdll.RtlSetLastWin32Error
10344158 7C8097E0 kernel32.TlsGetValue
1034415C E8ED396D
10344160 7C810C2E kernel32.SetFilePointer
10344164 9D591745
10344168 BAACD0C0
1034416C 7C809C98 kernel32.MultiByteToWideChar
10344170 8F2D6606
10344174 7C80A530 kernel32.GetStringTypeW
10344178 746A6CEA MSCTF.746A6CEA
1034417C 7C80CD48 kernel32.LCMapStringW
10344180 E5D1BBB8
10344184 7C810F98 kernel32.HeapDestroy
10344188 7C812C56 kernel32.HeapCreate
1034418C 7C809B84 kernel32.VirtualFree
10344190 7C809AF1 kernel32.VirtualAlloc
10344194 8D1D27D2
10344198 3741FCF5
1034419C 00000000
103441A0 65247650
103441A4 77D24A4E user32.EndDialog
103441A8 77D2AAFD user32.PostMessageA
103441AC C2E42721
103441B0 77D1869D user32.ReleaseDC
103441B4 77D186C7 user32.GetDC
103441B8 77D3C2E7 user32.SendDlgItemMessageA
103441BC 00000000
103441C0 72F74D40 winspool.ClosePrinter
103441C4 72F77A78 winspool.EndDocPrinter
103441C8 72F77837 winspool.WritePrinter
103441CC 72F80FE1 winspool.AbortPrinter
103441D0 72F841E7 winspool.StartDocPrinterA
103441D4 72F83757 winspool.OpenPrinterA
103441D8 72F7B041 winspool.EnumPrintersA
103441DC 00000000
103441E0 009C11D0 appdfenc.#3
103441E4 00000000
103441E8 009116F0 imgcvt.BmptoImage
103441EC 00911120 imgcvt.CreateMutilTiff
103441F0 009111D0 imgcvt.Bmp2MutilTiff
103441F4 009111B0 imgcvt.CloseMutilTiff
RVA:344010,size我就输入200(不去细算了)
再次使用Import REC手动查找输入表,发现还有部分函数没有识别出来,如下图
6.从第一个函数0034402C开始吧,hr 0034402C下硬件访问断点,SHIFT+F9运行
10001000 /$ 8B4C24 04 mov ecx,dword ptr ss:[esp+0x4]
10001004 |. 8B5424 0C mov edx,dword ptr ss:[esp+0xC] ; kernel32.7C817077
10001008 |. 53 push ebx
10001009 |. 55 push ebp
1000100A |. 8B19 mov ebx,dword ptr ds:[ecx] ; user32.77D2AF62
1000100C |. 8B4C24 10 mov ecx,dword ptr ss:[esp+0x10]
10001010 |. 33C0 xor eax,eax
10001012 |. 56 push esi ; Loaddll.00403000
开始F8单步跟,有耐心的找magic jump吧,可不想一个个慢慢还原......
...
...
...
n小时后,还是不知道怎么修复,宣告失败!(F8过来时已经都加密了)
0x4动态调试分析(OD)
1.为啥要纠结dll脱壳,反正主程序没有加壳,直接干不就完事了,直接MessageBOX断点,成功的断下来了,堆栈窗口返回
00408FA3 |. 50 push eax ; |hOwner = 001206BC ('Register',class='TRegDlg')
00408FA4 |. E8 63B50C00 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
2.接下来就向上回溯,看从哪里跳转过来的,定位到
00408E35 |> \51 push ecx ; xpstopdf.004D72BD
00408E36 |. 53 push ebx
00408E37 |. E8 50FFFFFF call xpstopdf.00408D8C
00408E3C |. 83C4 08 add esp,0x8
00408E3F |. 3C 01 cmp al,0x1
00408E41 |. 0F85 45010000 jnz xpstopdf.00408F8C ;关键跳
3.想也不用想,这个程序绝对把注册信息写入注册表了,爆破肯定没用的,咋也不想爆破,老老实实练习追码吧,截张图丢这。
4.再看刚刚那个比较,关键跳上面就是一个关键call(00408E37),两个push的就是我们输入字符串的长度和字符串,那还等啥,我们随便输入123456,进行跟
00408D8C /$ 55 push ebp
00408D8D |. 8BEC mov ebp,esp
00408D8F |. 53 push ebx
00408D90 |. 56 push esi
00408D91 |. 57 push edi
00408D92 |. 8B5D 0C mov ebx,[arg.2] ; 我输入的字符串123456,给ebx
00408D95 |. 85DB test ebx,ebx ; 判断是否为空,否则跳向失败
00408D97 |. 74 0C je short xpstopdf.00408DA5
00408D99 |. 53 push ebx
00408D9A |. E8 BDB80A00 call xpstopdf.004B465C ; 唯一的一个call,必须进去跟
00408D9F |. 59 pop ecx ; 将123456给ecx,出栈
00408DA0 |. 83F8 10 cmp eax,0x10 ; 判断eax的长度是否为0x10,也就是16
00408DA3 |. 74 04 je short xpstopdf.00408DA9 ; 是就跳
00408DA5 |> 33C0 xor eax,eax ; 清空eax,走向失败
00408DA7 |. EB 39 jmp short xpstopdf.00408DE2
00408DA9 |> 0FBE73 03 movsx esi,byte ptr ds:[ebx+0x3] ; 取输入的字符串第4个数
00408DAD |. 8BC6 mov eax,esi
00408DAF |. 0FBE7B 09 movsx edi,byte ptr ds:[ebx+0x9] ; 取输入的字符串第10个数
00408DB3 |. 03C7 add eax,edi ; 两个数带符号相加,值给eax
00408DB5 |. 3D 9B000000 cmp eax,0x9B ; eax == 155
00408DBA |. 75 24 jnz short xpstopdf.00408DE0 ; 不相等就跳
00408DBC |. 8BCE mov ecx,esi
00408DBE |. 2BCF sub ecx,edi
00408DC0 |. 8BC1 mov eax,ecx
00408DC2 |. 99 cdq ; 把edx扩展为eax的高位,以宽字符表示
00408DC3 |. 33C2 xor eax,edx
00408DC5 |. 2BC2 sub eax,edx
00408DC7 |. 83C0 41 add eax,0x41
00408DCA |. 0FBE53 06 movsx edx,byte ptr ds:[ebx+0x6]
00408DCE |. 3BC2 cmp eax,edx
00408DD0 |. 75 0E jnz short xpstopdf.00408DE0
00408DD2 |. 8B45 08 mov eax,[arg.1]
00408DD5 |. C680 34030000>mov byte ptr ds:[eax+0x334],0x1
00408DDC |. B0 01 mov al,0x1
00408DDE |. EB 02 jmp short xpstopdf.00408DE2
00408DE0 |> 33C0 xor eax,eax ; 跳到这里的不一定就是成功
00408DE2 |> 5F pop edi ; 调到这里的一定都是失败
00408DE3 |. 5E pop esi ; 0012F0C8
00408DE4 |. 5B pop ebx ; 0012F0C8
00408DE5 |. 5D pop ebp ; 0012F0C8
00408DE6 \. C3 retn
5.对算法没有头绪的时候,可以试试IDA,也许能解决你的困惑。
0x5静态调试分析(IDA)
1.OD分析了半天,也没看出算法,试试IDA
char __cdecl sub_408D8C(int a1, char *s)
{
char result; // al@3
int v3; // edi@4
if ( s && strlen(s) == 16 )
{
v3 = s[9];
if ( v3 + s[3] != 155 || abs(s[3] - v3) + 65 != s[6] )
{
result = 0;
}
else
{
*(_BYTE *)(a1 + 820) = 1;
result = 1;
}
}
else
{
result = 0;
}
return result;
}
很清晰的看到,我输入的字符串为16位,然后满足v3 + s[3] == 155 和 abs(s[3] - v3) + 65 == s[6]这两个条件就是正确的注册码。
2.建立数学方程
s[3] + s[9] = 155
|s[3] - s[9]| + 65 != s[6]
是一个一元三次方程,只有两个等式。
3.对照ASCII码表,口算都能得出一个注册码,qwe7tyniodzxcvbn。
7(55) + d(100) = 155
|7(55) - d(100)| (45) + 65 = n(110)
0x6注册机的编写
1.s算法的核心在于两个if语句
if (s && strlen(s) == 16)
if (s[9] + s[3] != 155 || abs(s[3] - s[9]) + 65 != s[6])
2.那我可以有如下思路:
1.随机生成一个长度为16的字符串
2.制定一个码表包含26个字母和数字
3.使用两层for循环将字符串[6]、[9]从码表中逐个替换
4.直到有匹配的字母或数字,否则提示s[3]找不到合理的s[6]和s[9],请重新输入
3.注册机源码如下:
#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#include <conio.h>
#define RandomStringLen (16+1) //控制随机字符串生成的长度
char * RandomString(char *str, int len)
{
int r = GetCurrentProcessId();//记录当前进程ID号作为随机数种子
int i = 0;//循环计数用
int intRand = 1000;//记录随机数
srand(r * r);//随机数播种
if (len < RandomStringLen)
{
perror("str分配的可用长度不足16!");
return 0;
}
while (1)//符合要求的字符就赋值给str
{
if (i == RandomStringLen - 1)//已经生成16位字符串了就退出循环
{
*(str + i) = '\0';//插入结束符
break;
}
intRand = rand() % 128;//生成随机数[0,128)区间
if (intRand >= 48 && intRand <= 57)//数字0-9
{
*(str + i) = intRand;
i++;
continue;
}
if (intRand >= 65 && intRand <= 90)//大写字母A-Z
{
*(str + i) = intRand;
i++;
continue;
}
if (intRand >= 97 && intRand <= 122)//小写字母a-z
{
*(str + i) = intRand;
i++;
continue;
}
}
//判断字符串是否成功生成
if (i == RandomStringLen)
{
return str;
}
return 0;
}
int main()
{
char str[100] = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";//码表
char *s = (char *) malloc(RandomStringLen);//保存注册码
RandomString(s, RandomStringLen);//生成16位随机字符串
printf("请输入一个你认为有注册码的字母或数字:\n");
s[3] = getchar();
printf("注册码计算中,请稍后...\n");
if (s[3] + s[9] != 155||abs(s[3] - s[9]) + 65 != s[6])
{
for (int i = 0; i < strlen(str); i++)//s9
{
for (int j = 0; j < strlen(str); j++)//s6
{
if (s[3] + s[9] == 155)
{
if (abs(s[3] - s[9]) + 65 == s[6])
{
printf("成功获取注册码一枚:%s\n", s);
system("pause");
return 0;
}
else
{
s[6] = s[j];//修改s6的值
continue;//直接进行下一次循环
}
}
else
{
break;//跳出内层循环
}
}
s[9] = s[i];//修改s[9]的值
if (i == strlen(str) - 1)//i已经到尽头了
{
printf("程序bug,%c注册码找不到,请再试一次!\n",s[3]);
}
}
}
else
{
printf("成功获取注册码一枚:%s\n",s);
}
system("pause");
return 0;
}
4.运行程序,得到注册码一枚
请输入一个你认为有注册码的字母或数字:
2
注册码计算中,请稍后...
成功获取注册码一枚:s4s2oyxoSiMMcNcB
请按任意键继续. . .
4.输入注册码,成功截图
0x4总结
1.才想起来还没学过dll怎么脱壳
2.分析算法真的比爆破好玩
3.写注册机能让你有目标的编写程序