FCG学员区资料,转载请注明
目标:apis32 v 2.5,
保护方式:RSA8
以前:ArchFire/ATA写过,但你看了这片文章
可能会有新的收获。
另一个目标:WMV to AVI MPEG DVD WMV Converter 1.4.8
保护方式:RSA256
http://www.alloksoft.com/wmv.htm
1.Brief Introduction to RSA
取自RSA-Tool 2的Help。
1.1. About RSA
RSA
is a Public Key Cryptosystem
developed in 1977 by Ronald Rivest,Adi Shamir and Leonard Adleman.Since
1.2. Parameters
P = 1st large prime number
Q = 2nd large prime number (sizes of P and Q should not differ too much!)
E = Public Exponent (a random number which must fulfill :
GCD(E, (P-1)*(Q-1))==1)
N = Public Modulus, the product of P and Q: N=P*Q
D = Private Exponent: D=E^(-1) mod ((P-1)*(Q-1))
Parameters N and E are public whereas D is -private- and must NEVER be published! P and Q are not longer needed after keygeneration and should be destroyed.
To obtain D from the public key (N, E) one needs to try splitting N in its both prime factors P and Q. For a large Modulus N (512 bit and more) with carefully chosen primefactors P and Q this is a very difficult problem.
All the security of the RSA encryption scheme relies on that integer factorization problem (tough there's no mathematical proof for it).To find out more read here:
http://www.rsasecurity.com/rsalabs/challenges/factoring/rsa155.html
1.3. Encryption
To encrypt a messageblock (M) (which must be < N), compute:
Ciphertext = C = M^E mod N.
Note: If the entire message (M) is > N it must be split into smaller
blocks with size < N
1.4. Decryption
To decrypt a given Ciphertext (C) to retrieve the Plaintext (M) as result,
compute: M=C^D mod N. The ' ^ ' sign in the above equations means 'power of', not XOR !
Note that the RSA scheme does also work the other way round:
C=M^D mod N and M=C^E mod N. It's on you how you implement it. Just ALWAYS
make SURE that you -NEVER- publish the private exponent D, P and/or Q !
2.Make a keygen for apis32
v2.5
2.1 Tools used
【Dynamic Debugger】OllyDBG v1.10 fly修改版
2.2 Trace registration code checking routine
好啦,开工,用OD载入目标apis32.exe,这个程序是加壳了的,弹出消息框“要继续分析吗?”,选择“否”,停留在入口点:
汇编指令注释:
PUSHFW
pushes
the bottom 16 bits of the flags register (or the whole flags register, on
processors below a 386) onto the stack.
PUSHAD
pushes, in succession, EAX
, ECX
, EDX
, EBX
, ESP
, EBP
, ESI
and EDI
on the stack, decrementing the stack pointer by a total
of 32. The
value of SP
or ESP
pushed is its original value, as it had before
the instruction was executed.
这是具有压缩壳特色的开头,壳的结尾必定是用popad,popfw,来还原寄存器。下面我们就搜索这两条指令,ctrl+S,搜索
Popad
Popfw
找到了:
光标停留在popad那一行,F2下断点,再F9运行,中断在popad,F7单步,执行jmp 00406360,后到达程序入口点:
然后用OllyDUMP插件Dump出程序,Rebuild import选项中选method 2,dump出的程序保存为aris32_unpacked.exe,运行,OK。
到达OEP的第二种方法(ESP定律):
载入apis32,下硬件断点:HR esp,(esp=0012FFC4)
F9运行,中断在:004195D2 - E9 89CDFEFF jmp apis32.00406360,这时注意观察:
ESP=0012FFC4.
与下断点时的esp相同。这就是堆栈平衡原理,压缩壳在运行解压的程序时,应保证堆栈的安全,否则程序会崩溃。
注释:
Hardware breakpoints or
BPM(Breakpoint on Memory) use the Debug Reg-
isters. In the archi-tectural
x86 (+386) 4 debug registers(DR0-DR3) exists in the processor. Sowe can
only set 4 BPM. The DR4 and DR5 are reserved registers, the DR6register
is for the debugger and the DR7 register still using for the type of
break for the DR0-DR3 registers.
脱壳完毕,下面就去跟踪脱壳后的程序,追踪注册码的检验过程。
先在注册窗口输入任意用户名和注册码,看其提示信息,然后再搜索字符串,找到提示信息,这样就定位到注册码校验的代码附近了。
右键,搜索,字符串,然后看到全部字符串:
敏感字符串真多啊!
如图,F2下断,再双击下断的那一行,来到程序中,往上翻,
看红色线,是从上面0040171D跳过来的。上面一句是je,如果注册码校验失败,je就跳了,注册码成功,je不跳,下一句跳到00401754。
再往上看:
00401711 |. E8
显然,这个call就是用来检验注册码的。光标停在00401711这一行,回车,进入这个call,
在这个call开头下断点。
重新载入脱壳后的程序,进入注册窗口,输入用户名:winndy,注册码:1234567890
断在00405040,执行call 00405370后,发现,eax=
同理,用户名的长度也要大于等于5。
更改注册码长度为16,1234567890abcdef,重新跟踪。
004050DA |. BE 41B74000 mov esi,aris32_u.0040B741
; ASCII "234567890abcdef"
004050DF |. BF 54B74000 mov edi,aris32_u.0040B754
; ASCII "123456780abcdef"
从这里我们可以看到,第9个字符被去掉了,因此注册码格式为:12345678-90abcdef。
004050E4 |> 57
/push edi
004050E5 |. E8 E6010000 |call aris32_u.004052D0
=è见下面
004050EA |. 8ACB
|mov cl,bl ;bl是计数器,初值为0
004050EC |.
004050EF |.
004050FC |. C606 00 |mov
byte ptr ds:[esi],0
004050FF |. 46
|inc esi
00405100 |. 80FB 08 |cmp
bl,8
00405103 |.^ 72 DF
\jb short aris32_u.004050E4
004050E5处调用的函数:
004052D0 /$ 8B
004052D4 |.
004052D6 |.
004052D8 |. 7E 04 jle short aris32_u.004052DE
004052DA |.
004052DC |. EB 02 jmp short aris32_u.004052E0
004052DE |> 04 D0 add
al,0D0
;39+D0=109
004052E0 |>
004052E3 |.
004052E6 |. 7E 09
jle short aris32_u
004052E8 |. C0E0 04 shl al,4
;左移4位
004052EB |. 80E9 37 sub cl,37
;
004052EE |.
上面这段程序的功能是讲输入的注册码字符串,每两个转化为一个byte,例:“
再往下面看。
00405300 /$ 53 push ebx
00405301 |. 55 push ebp
00405302 |. 8B
00405306 |. 56 push esi
00405307 |. 57 push edi
00405308 |. 8B
0040530E |. 2BFD sub edi,ebp
00405310 |.
00405314 |. EB 04
jmp short aris32_u
00405316 |> 8B
0040531D |. 33D2 |xor edx,edx
00405324 |. C74424 14 07000>|mov dword ptr ss:[esp+14],7 //这是循环计数器
//取一个byte,由注册码变换而来
00405331 |> 8BD7
|/mov edx,edi
00405333 |. 0FAFC2 ||imul eax,edx
00405336 |. 3D 99880000 ||cmp eax,8899
0040533B |. 7E
0040533D |. 99 ||cdq
0040533E |. BB 99880000 ||mov ebx,8899
00405343 |. F7FB ||idiv ebx
00405345 |. 8BC2 ||mov eax,edx //Mod 8899,取余数
00405347 |> 8B5424 14 ||mov edx,dword ptr ss:[esp+14]
0040534B |.
00405350 |.^ 75 DF |\jnz short aris32_u.00405331
00405352 |. 99 |cdq
00405353 |. BF BB000000 |mov edi,0BB
00405358 |. F7FF
|idiv edi
0040535B |.
0040535E |. 8816 |mov byte ptr ds:[esi],dl //mod 0BB,保存余数
00405360 |. C60429 00 |mov byte ptr ds:[ecx+ebp],0
00405364 |.^
00405366 |.
00405367 |. 5E
pop esi
00405368 |. 5D pop ebp
00405369 |. 5B pop ebx
继续往下看:
0040514B |.
0040514E |.
00405152 |. 73 30 jnb short aris32_u.00405184 ;长度<8时,补到8位
00405154 |. 8B5424 10 mov edx,dword ptr ss:[esp+10]
00405158 |. BF C
0040515D |. 81E2 FF000000 and edx,0FF
00405163 |.
00405166 |.
0040516E |. F7D1 not ecx
00405170 |. 2BF9 sub edi,ecx
00405172 |. 8BC1 mov
eax,ecx
00405174 |. 8BF7
mov esi,edi
00405176 |. 8BFA
mov edi,edx
00405178 |. C1E9 02 shr
ecx,2
0040517B |. F3:A5 rep movs dword ptr es:[edi],dword pt>
0040517D |. 8BC8 mov ecx,eax
00405182 |. F3:A4 rep movs byte ptr es:[edi],byte ptr >
00405184 |> C605 66B74000 0>mov byte ptr ds:[40B766],0 ;用户名长度补足8位了
0040518B |. B9 54B74000 mov ecx,aris32_u.0040B754 ;ecx指向注册码变换二来的8个bytes
00405190 |. BE 08000000 mov esi,8
00405195 |>
00405197 |.
00405199 |. 73 0E
|jnb short aris32_u
0040519B |. 33D2 |xor edx,edx ;标志直接清0
0040519D |. 25 FF000000 |and eax,0FF
004051AB |. 25 FF000000 |and eax,0FF
004051B0 |.
004051B3 |. 33D0 |xor edx,eax
; 比较,如果edx=eax,会置edx为零
004051B5 |> 03EA |add ebp,edx
; ebp保存比较结果,如果每次edx都为0,则ebp最终也为0
004051B7 |. 41
|inc ecx
004051B8 |. 4E
|dec esi
004051B9 |.^ 75 DA \jnz short aris32_u.00405195
004051BB |.
004051BD |.
004051BE |. 85ED test ebp,ebp ; ebp为0吗?
至此,整个算法跟踪过来了。在下面一节里,总结一下算法。
2.3 algorithm summary
回顾一下验证过程:
1.注册码长度大于等于16
2.用户名长度大于等于5,不足8位的取用户名的前几位将其补足到8位。
3.忽略注册码的第9位,第9位定为-.
4.依次取注册码的两位,保存为一个byte,为方便起见,设注册码为十六进制字符(当然也可一为非十六进制字符,因为程序没有去验证),且注册码长度为17,格式为:XXXXXXXX-XXXXXXXX。
5.将8个bytes一次与50h,…57h异或。
6.取出一个byte,记为m,n=n×m,n的初值为1,如果n大于8899H,则mod 8899H,余数赋给n。这样循环其次7次。
7.将byte Mod 0BBh,保存余数。
8.将这最终的8个bytes与用户名相比较,如果相等,则注册成功。
在下面的两节里,将用两种不同的思路来做出注册机来。
2.4 write a keygen :Searching Table
如何求出上面算法的逆运算呢?
倒着推导。
给定一个用户名,如winndy,取一个byte,难以求得注册码相应的一个byte。因为第6步不好求逆。但大家不要忘了第6步取的是一个byte,最多也只有256个,这样就可以采用穷举法:
对于从0到255的这些byte,按照第6和第7步,将每个byte所对应的byte算出来,这样可以得到一个对应的表格,然后根据用户名的byte做为index,查找表格就可以得到注册码的byte,然后再与相应的50h,..,57h异或,即可得到注册码。
思路非常简单。
我用VB编了简单的程序,制作了表格,写出了注册机。详见源码。
注册机中加入了xm音乐,为此,花了我不少时间,最后找到uFMOD,Excellent!
2.5 write a keygen :RSA
如果你了解RSA,则可看出,第6步实际上是RSA运算。
参考了 ArchFire/ATA
@
2.4节的注册机是我自己做的,后来参考看雪精华,才发现这篇好文章。在此之前,我未接触过用RSA的程序,也难怪啊!
废话打住,下面介绍RSA8:
第6步:C=((M^7) mod 8899) mod 0bb=(M^7) mod 0bb, 因为 8899 mod 0bb=0
因此大数n=0BBh。将n分解为两个素数的积,n=11*0B,p=11h,q=0Bh,
f=(p-1)*(q-1)= 10*
即为求e的模逆(mod f).这需要有数论的知识,
下面给出一个例子,手工用辗转相除法求模逆,实际中,用te!的RSA-Tool可以计算出d,附录中还给出其它相关资料:
好了,本例中,我们的d为:d=17h。有兴趣的可以去算一下,加深理解。
于是,由用户名求注册码的过程即为:
M=(C^17) mod 0BBh
详见注册机源码,与穷举法比较一下,好好体会。
2.6 Some Questions and Assignment
1.程序中并没有对输入的byte作要求,实际RSA算法应使C小于0BBh,因此我们的注册码不是唯一的,这是我通过穷举法发现的。给出一个例子:
Winnhijk
7B
B
B
B
我们在求得M后,M是小于0BBh的,事实上如果我们将M加上0BBh,如果M仍小于256,即仍为一个byte,继续参加后面的xor运算,得到的注册码仍是正确的。
在方法1,由穷举得到的表格中可以很明显地看出来。
2.根据验证过程,注册码长度大于等于16都是可以的,而且去掉第9个字符后的注册码个数也不一定为偶数,注册码也不一定要为16进制字符,试想一下:
考虑把最后两个字符换成一个非16进制字符,比如z(ascii为7Ah),7Ah+C9h=143,由z求得的al等于多少?z代替哪两个16进制字符?请下去自己尝试一下。(答案是F0)
F0 xor 57=A7 ,A7――>28(()
Winnhij(
7B
反之,给出两个十六进制字符,其对应的非十六进制字符存在吗?如果存在,是多少?
3.请用其他编程语言,写出注册机。
4.注册码保存在注册表中:HKEY_LOCAL_MACHINE\SOFTWARE\APIS32
上面是RSA8,加密的明文是8bytes,下面来看一个RSA256,加密的数是256个bits,看作一个256进制的数。在内存中有32个bytes,高位在后,低位在前,这一点务必要清楚。
3.Make a keygen for WMV
to AVI MPEG DVD WMV Converter v1.4.8
3.1 Tools used
【Dynamic Debugger】OllyDBG v1.10 fly修改版
【Other】 RSA-Tool 2 v17 by tE! [TMG]
PPSIQS V1.1 by Satoshi Tomabechi 2001
PPSIQS V1.1 download URL:http://www.asahi-net.or.jp/~KC2H-MSM/cn/
3.2Trace registration code checking routine
载入WMV to AVI MPEG DVD WMV Converter.exe,这个程序是没有加壳的。
首先输入winndy,1234567890,看看提示信息。
右键,搜速,字符串:
看到错误提示字符串了吧?老规矩,下断。
注意0041CA63处的jnz,前面0041CA59处的call就是注册码验证关键部分。
在0041CA
同理,上面也下断。
输入假注册码后,停留在0042E311。
F7进入00401AA5:
00401AA5 $ /E9
注意上面这些dword,一共256个bits,组成大数n。
D esp+60
下面是内存中的数据:
RSA256,这是256进制数,低位在前,高位在后, N=CFB 利用RSA-Tool将其转化为十进制数:
93951393961625247111454448822519571285412527322314031847324223587660098765931 也许有人要问,究竟是怎么识别算法为RSA256的,这主要靠经验了,首先需要熟悉各种加密算法,了解各种算法的常数,其次还要注意观察特征。都不行时,还得靠慢慢分析,这需要有很大的耐心。
;注意这一句,这也是RSA的一个特征,E = Public
Exponent,E一般等于10001H。 注意这个格式化字符串,这也就是注册码的格式。 所以下次就按照注册码的正确格式来输入假注册码: 12345678-90abcdef-12345678-90abcdef-12345678-90abcdef-12345678-90abcdef 经过变换后的注册码为: 可以看到第7个dword和第8个dword变了。设第i个dword为code(i), Code(8)= (code(5)+code(4)+code(3)+code(2))
Xor code(8) code(7) =(code(1)+code(6))
xor code(7) 下面就用RSA256对这个256进制数进行解密,解密出来的数据经过高低位字节交换后,是用户名,则注册成功。 经过这个call之后,注册码数据区变为: D 通过观察,上面这个循环将一个dword的高低字节交换。 变换为: D ecx 关键的比较,注册码由RSA解密,与用户名进行比较。 现在回顾一下注册码的验证过程: 1.注册码格式为: 12345678-90abcdef-12345678-90abcdef-12345678-90abcdef-12345678-90abcdef 2.注册码的第7段和第8段要由前6段来修改。 3.修改后的256bits数据,经RSA解密,得到一个256bits数据。 4.此256bits数据的dword高低位交换,交换后如果等于用户名,则注册成功。 知道算法以后,下面就来写注册机,我用的编程语言为Win32ASM,编程工具:MASMV8.2。 最难的就是大数N的分解,N=p*q, RSA-Tool分解太慢,最好的工具是PPSIQS V1.1 by Satoshi Tomabechi 2001。 经过1个小时多一点(P4 93951393961625247111454448822519571285412527322314031847324223587660098765931
= P39 * P39 P39 = 304268020309461933344817079717793018591 P39 = 308778404861838864852708240341186890741 打开RSA-Tool ,将它们转为十六进制数: E4E7E39EE5E (304268020309461933344817079717793018591) E (308778404861838864852708240341186890741) 计算D:
D=3CE =27535870961668946496129164486976149735850411661692136930155875617194596807873 利用C=M^D mod N 便可以计算注册码了。 还是倒推。 用户名为256bits数据, 把用户名的ascii码每个dword经过高低字变换,保存为M。 然后调用C=M^D mod N,计算注册码C,C的第7段和第8段再经过Xor处理,再用格式化字符串输出,即为注册码: "%08lX-%08lX-%08lX-%08lX-%08lX-%08lX-%08lX-%08lX",关于wsprintf函数,见附录。 注册机采用了biglib v.0.01e by fleur。感谢作者! 注册码保存在data.ini文件中,读取注册码:GetPrivateProfileString,写入注册码:WritePrivateProfileString。若用户名前后有空格,可以注册成功,但是下次运行时仍提示未注册。原因在于GetPrivateProfileString函数,忽略了字符串前后的空格。
仔细体会一下,是如何识别出RSA算法的,例子中,识别出算法后,很多call的功能都不了解,但通过观察数据区的变化,仍写出了注册机。事实上,对于标准算法,例如,MD5等,通常都是靠识别出特征,然后写出注册机的。即使看MD5的高级语言源码也很困难,更不用说去看汇编版的了,所以熟悉各种标准算法的特征是很关键的。
有兴趣的可以去尝试进入call跟踪一下,看能不能看懂某个call的功能,再用高级语言实现之。
还有一个任务,请尝试用其他语言写出注册机。另外,网上有个miracl库,全称是MIRACL -
Multiprecision Integer and Rational Arithmetic C Library。
本文介绍了用RSA算法保护的软件的破解方法。从简单的RSA8到复杂的RSA256,并且给出了RSA8的穷举方法。还介绍了RSA-Tool 2 by te! [TMG]的使用方法,以及利用ppsiqs v1.1分解大数。还介绍了OllyDBG动态调试工具的基本使用方法。为了补充数论的知识,还给出了一个手动求模逆的例题。 附录一: Euclid算法 http://zhdf.blog.edu.cn/user2/52478/archives/2006/1073022.shtml 今天在看RSA加密算法的时候看到了可以用扩充的euclid算法来简化d的计算。一查才发现原来euclid算法算法就是下面这个式子: GCD
(a, b) = GCD (b, a % b) 下面这个是著名求最大公约数的辗转相除算法的代码实现: int Euclid_Algorithm (int m, int n) if (!m || !n)
return 0; while (1) { 而扩展的euclid算法的原理是这样的: k*k-1=1(mod 26) k-1叫k的模逆, 可以用扩展的Euclid算法求出来。 ax = 1 (mod 26)
其中x = a-1 相当于 ax + by = 1,b = 26,求满足条件的一组x, y。当然我们只要x就好. 现在考虑一般的 ax + by = 1 如何求解。 因为满足条件的x, y存在的条件是GCD(a,
b) = 1. 然后有 ax + by = GCD (a, b) 而同时有 bx' + (a % b)y' = GCD (b, a % b) 由Euclid定理GCD (a, b)
= GCD (b, a % b) 所以, 有ax + by =
bx' + (a % b)y'= bx' + (a - [a/b]*b)y'= bx' + ay' - [a/b]*b y'= ay' + b(x'
- [a/b]y') 对应, 得x = y',y = x' - [a/b]y'。[a/b]是a/b再取整。 特别的, 在b = 0的时候, GCD (a, b) = a = a * 1 + b * 0 即 x = 1, y = 0 数论对于很多算法看来还是很有用的…… 附录二: sscanf <stdio.h> int sscanf(
const char *buffer,
const char *format [,
argument ] ... ); Parameters buffer Stored data format Format-control string. For more information,
see Format Specifications. argument Optional arguments Return Value Each of these functions returns the number
of fields successfully converted and assigned; the return value does not
include fields that were read but not assigned. A return value of 0 indicates
that no fields were assigned. The return value is EOF for an error or if
the end of the string is reached before the first conversion. Remarks The sscanf function reads data from buffer
into the location given by each argument. Every argument must be a pointer
to a variable with a type that corresponds to a type specifier in format.
The format argument controls the interpretation of the input fields and
has the same form and function as the format argument for the scanf function.
If copying takes place between strings that overlap, the behavior is undefined. Format Specification Fields: scanf and
wscanf Functions A format specification has the following
form: %[*] [width] [{h | l | I64 | L}]type The format argument specifies the interpretation
of the input and can contain one or more of the following: White-space characters: blank (' '); tab
('\t'); or newline ('\n'). A white-space character causes scanf to read,
but not store, all consecutive white-space characters in the input up to
the next non–white-space character. One white-space character in the format
matches any number (including 0) and combination of white-space characters
in the input. Non–white-space characters, except for
the percent sign (%). A non–white-space character causes scanf to read,
but not store, a matching non–white-space character. If the next character
in stdin does not match, scanf terminates. Format specifications, introduced by the
percent sign (%). A format specification causes scanf to read and convert
characters in the input into values of a specified type. The value is assigned
to an argument in the argument list. The format is read from left to right.
Characters outside format specifications are expected to match the sequence
of characters in stdin; the matching characters in stdin are scanned but
not stored. If a character in stdin conflicts with the format specification,
scanf terminates, and the character is left in stdin as if it had not been
read. When the first format specification is
encountered, the value of the first input field is converted according to
this specification and stored in the location that is specified by the first
argument. The second format specification causes the second input field
to be converted and stored in the second argument, and so on through the
end of the format string. An input field is defined as all characters
up to the first white-space character (space, tab, or newline), or up to
the first character that cannot be converted according to the format specification,
or until the field width (if specified) is reached. If there are too many
arguments for the given specifications, the extra arguments are evaluated
but ignored. The results are unpredictable if there are not enough arguments
for the format specification. Each field of the format specification
is a single character or a number signifying a particular format option.
The type character, which appears after the last optional format field,
determines whether the input field is interpreted as a character, a string,
or a number. The simplest format specification contains
only the percent sign and a type character (for example, %s). If a percent
sign (%) is followed by a character that has no meaning as a format-control
character, that character and the following characters (up to the next percent
sign) are treated as an ordinary sequence of characters, that is, a sequence
of characters that must match the input. For example, to specify that a
percent-sign character is to be input, use %%. An asterisk (*) following the percent
sign suppresses assignment of the next input field, which is interpreted
as a field of the specified type. The field is scanned but not stored ―――By windy【FCG】【DFCG】【PYG】
―――2006.3.10完稿
I退殏
3.3 algorithm summary
3.4 write a keygen :RSA
3.5
Some Questions and Assignment
4.Conclusion
5.appendix
{
int temp = m;
if (m < n)
{m = n; n = temp;}
if (!(m = m % n)) return n;
if (!(n = n % m)) return m;
}
}