前言
粗略浏览了第六章,虽然书中详细给出各种加密算法的原理,但是对于没有密码学基础的我来说,还是难以理解:(
慢慢看吧
(冲浪发现的视频,作者见左上角水印)
单向散列算法(哈希算法)
哈希算法具有以下的特征:
- 往函数输入相同的值时,函数的输出相同
- 如果往函数中输入不同的值,得到的结果(通常)差别很大。
- 算法不可逆,意味着没有办法从算法的输出逆推得到输入
由于哈希算法的不可逆性,服务端通常使用哈希算法+盐值的方式存放密码,大大增加了解密者得出原密码的难度。
之前看过这种存放方式: md5(md5(password)+salt)
md5算法
一些资料
说实话,我目前能看懂书中介绍的MD5计算流程,但是要我分析代码时能反应出这是md5算法。。。。不太行
分析MD5KeyGenMe.exe
程序
尝试运行程序
用exeinfo pe查壳,使用Krypto ANALyzer
插件分析程序使用的加密算法。
发现是md5加密,使用OD进一步分析程序。
在GetDlgItemTexr
函数下断点,Name输入123456, Serial Number输入abcdef
0040114A |. 68 C9000000 push 0xC9 ; /Count = C9 (201.)
0040114F |. A4 movs byte ptr es:[edi],byte ptr ds:[esi] ; |
00401150 |. 8BB424 E00300>mov esi,dword ptr ss:[esp+0x3E0] ; |
00401157 |. 894424 21 mov dword ptr ss:[esp+0x21],eax ; |
0040115B |. 51 push ecx ; |Buffer = 0019F64C
0040115C |. 66:894424 29 mov word ptr ss:[esp+0x29],ax ; |
00401161 |. 68 E8030000 push 0x3E8 ; |ControlID = 3E8 (1000.)
00401166 |. 56 push esi ; |hWnd = 000808B8 ('MD5 *KeyGenMe*',class='#32770')
00401167 |. 885C24 20 mov byte ptr ss:[esp+0x20],bl ; |
0040116B |. 884424 33 mov byte ptr ss:[esp+0x33],al ; |
0040116F |. FFD5 call ebp ; \GetDlgItemTextA
// 获取用户名
00401171 |. 8BF8 mov edi,eax
00401173 |. 3BFB cmp edi,ebx
00401175 |. 0F84 0E010000 je MD5KeyGe.00401289
0040117B |. 8D5424 60 lea edx,dword ptr ss:[esp+0x60]
0040117F |. 68 C9000000 push 0xC9 ; /Count = C9 (201.)
00401184 |. 52 push edx ; |Buffer = NULL
00401185 |. 68 E9030000 push 0x3E9 ; |ControlID = 3E9 (1001.)
0040118A |. 56 push esi ; |hWnd = 000808B8 ('MD5 *KeyGenMe*',class='#32770')
0040118B |. FFD5 call ebp ; \GetDlgItemTextA
// 获取序列号
0040118D |. 83F8 13 cmp eax,0x13
00401190 |. 0F85 F3000000 jnz MD5KeyGe.00401289
// 如果序列号的长度不等于19,跳转
我将序列号改为1234567891234567891 , 满足序列号长度为19的要求,重新运行程序。
00401196 |. 8A4C24 64 mov cl,byte ptr ss:[esp+0x64]
0040119A |. B0 2D mov al,0x2D
0040119C |. 3AC8 cmp cl,al
0040119E |. 0F85 E5000000 jnz MD5KeyGe.00401289
004011A4 |. 384424 69 cmp byte ptr ss:[esp+0x69],al
004011A8 |. 0F85 DB000000 jnz MD5KeyGe.00401289
004011AE |. 384424 6E cmp byte ptr ss:[esp+0x6E],al
004011B2 |. 0F85 D1000000 jnz MD5KeyGe.00401289
右键[esp+0x64]
->数据窗口中跟随->内存地址,发现[esp+0x64]
指的是序列号的第五个字节。
以此类推,这段代码验证了序列号第五,第十,第十五个字节是不是-
,如果不是,验证失败。
我将序列号改为1234-6789-1234-6789,重新运行,继续分析程序
004011B8 |. 8B4C24 65 mov ecx,dword ptr ss:[esp+0x65] ; 序列号第六个字节
004011BC |. 8B4424 60 mov eax,dword ptr ss:[esp+0x60] ; 序列号第一个字节
004011C0 |. 8B5424 6A mov edx,dword ptr ss:[esp+0x6A] ; 序列号第十一个字节
004011C4 |. 894C24 14 mov dword ptr ss:[esp+0x14],ecx
004011C8 |. 894424 10 mov dword ptr ss:[esp+0x10],eax
004011CC |. 8B4424 6F mov eax,dword ptr ss:[esp+0x6F]
004011D0 |. 8D8C24 280100>lea ecx,dword ptr ss:[esp+0x128] ; md5的框架
004011D7 |. 895424 18 mov dword ptr ss:[esp+0x18],edx
004011DB |. 51 push ecx
004011DC |. 894424 20 mov dword ptr ss:[esp+0x20],eax
004011E0 |. E8 CB000000 call MD5KeyGe.004012B0
{
// 初始化MD5
004012B0 /$ 8B4424 04 mov eax,dword ptr ss:[esp+0x4]
004012B4 |. 33C9 xor ecx,ecx
004012B6 |. 8948 14 mov dword ptr ds:[eax+0x14],ecx
004012B9 |. 8948 10 mov dword ptr ds:[eax+0x10],ecx
004012BC |. C700 01234567 mov dword ptr ds:[eax],0x67452301
004012C2 |. C740 04 89ABC>mov dword ptr ds:[eax+0x4],0xEFCDAB89
004012C9 |. C740 08 FEDCB>mov dword ptr ds:[eax+0x8],0x98BADCFE
004012D0 |. C740 0C 76543>mov dword ptr ds:[eax+0xC],0x10325476
004012D7 \. C3 retn
}
很明显,004012B0
函数用于初始化md5,因为出现了四个md5标志常数。
继续分析: 下面的A指的是md5初始化的常数01234567h
004011E5 |. 8D9424 4C0200>lea edx,dword ptr ss:[esp+0x24C]
004011EC |. 57 push edi ; name的长度
004011ED |. 8D8424 300100>lea eax,dword ptr ss:[esp+0x130]
004011F4 |. 52 push edx ; name的地址
004011F5 50 push eax ; A的地址
004011F6 |. E8 E5000000 call MD5KeyGe.004012E0
猜测004012E0函数作用
猜测得到的数据是用户名+www.pediy.com进行MD5的结果,验证一下
在数据窗口跟踪寄存器的值,正好eax是第一个被我们分析的寄存器
可以发现用户名和www.pediy字符串连一起了。具体函数函数代码不分析了,因为看不懂:(
004011FB |. 83C4 10 add esp,0x10
004011FE |. 8D4C24 24 lea ecx,dword ptr ss:[esp+0x24]
00401202 |. 51 push ecx ; /String = NULL
00401203 |. FF15 04604000 call dword ptr ds:[<&KERNEL32.lstrlenA>] ; \lstrlenA
00401209 |. 50 push eax
; www.pendiy.com的长度
0040120A |. 8D5424 28 lea edx,dword ptr ss:[esp+0x28]
; www.pendiy.com
0040120E |. 8D8424 2C0100>lea eax,dword ptr ss:[esp+0x12C]
00401215 |. 52 push edx
; www.pendiy.com
00401216 |. 50 push eax
; A的地址
00401217 |. E8 C4000000 call MD5KeyGe.004012E0
0040121C |. 8D8C24 340100>lea ecx,dword ptr ss:[esp+0x134]
;算法的结果存放位置
00401223 |. 8D9424 8C0100>lea edx,dword ptr ss:[esp+0x18C]
0040122A |. 51 push ecx
0040122B |. 52 push edx
0040122C |. E8 5F010000 call MD5KeyGe.00401390
猜测401390函数作用
发现ecx
指向的位置有128位的二进制数改变了,因此00401390
应该就是MD5加密算法
(由00 00 00 变成上图的结果)
00401231 |. 83C4 14 add esp,0x14
00401234 |. 33C0 xor eax,eax
00401236 |> 8A8C04 800100>/mov cl,byte ptr ss:[esp+eax+0x180]
0040123D |. 83E1 1F |and ecx,0x1F
//0x1F的二进制表示为11111,一个二进制数与1等于它本身,所以
//and ecx,0x1F的运算结果为ecx%32
00401240 |. 40 |inc eax
00401241 |. 83F8 10 |cmp eax,0x10
00401244 |. 8A540C 3C |mov dl,byte ptr ss:[esp+ecx+0x3C]
00401248 |. 889404 0F0300>|mov byte ptr ss:[esp+eax+0x30F],dl 结果放入缓冲区
0040124F |.^ 7C E5 \jl short MD5KeyGe.00401236
00401251 |. 8D8424 100300>lea eax,dword ptr ss:[esp+0x310]
00401258 |. 8D4C24 10 lea ecx,dword ptr ss:[esp+0x10]
0040125C |. 50 push eax ; /String2 = ""
0040125D |. 51 push ecx ; |String1 = "#Eg壂惋簶vT2?
0040125E |. FF15 00604000 call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
观察[esp+0x3C+ecx]
处的数据 ,结合ecx的取值为0-31,发现这是一张替换表,ecx表示下标。
最后将输入的序列号(不含-
)与上面代码计算出的序列号进行比较
程序加密原理讲明白了,接下来要写出注册机
重新梳理程序加密逻辑:程序在用户名字符串的末尾连接上www.pediy.com
字符串,进行MD5加密。遍历加密结果,将每一个字节%32,将其作为替换表的下标,找到对应的字符。最后将加密结果与输入的序列号(不含-
)进行比较
写出注册机
我首先尝试在cpp中使用openssl第三方库,但是用一天时间没成功导入,于是选择python下的hashlib
库调用MD5函数
import hashlib
table = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
name = input("请输入字符串")
md5_result = hashlib.md5()
md5_result.update(name.encode())
md5_result.update(b"www.pediy.com")
print("MD5结果",md5_result.hexdigest())
for i in range(0,len(md5_result.hexdigest()) , 2):
index = int(md5_result.hexdigest()[i:i+2],16) % 32
if i == 8 or i == 16 or i == 24:
print('-',end='')
print(table[index],end = '')
# 请输入字符串123456
# MD5结果 c52f2de7041dc12e26829e219867b035
# 7HF9-6X3G-84Y3-S9JP
md5结束
终于写完MD5了,休息一下
使用该图片已征求本人同意:)
sha1算法
复习一下MD5的消息处理函数
F
(
X
,
Y
,
Z
)
=
(
X
&
Y
)
∣
(
(
X
˜
)
&
Z
)
G
(
X
,
Y
,
Z
)
=
(
X
&
Z
)
∣
(
Y
&
(
Z
˜
)
)
H
(
X
,
Y
,
Z
)
=
X
⊕
Y
⊕
Z
I
(
X
,
Y
,
Z
)
=
Y
⊕
(
X
∣
(
Z
)
)
F(X,Y,Z) = (X\&Y)|((\~X)\&Z)\\ G(X,Y,Z) = (X\&Z) |(Y\&(\~Z))\\ H(X,Y,Z) = X\oplus Y\oplus Z\\ I(X,Y,Z) =Y\oplus (X|(~Z))
F(X,Y,Z)=(X&Y)∣((X˜)&Z)G(X,Y,Z)=(X&Z)∣(Y&(Z˜))H(X,Y,Z)=X⊕Y⊕ZI(X,Y,Z)=Y⊕(X∣( Z))
这是sha1的处理函数
f
(
x
)
=
{
(
B
&
C
)
∣
(
(
B
˜
)
&
D
)
0
≤
t
≤
19
B
⊕
C
⊕
D
20
≤
t
≤
39
(
B
&
C
)
∣
(
B
&
D
)
∣
(
C
&
D
)
40
≤
t
≤
59
B
⊕
C
⊕
D
60
≤
t
≤
79
f(x)= \left\{ \begin{array}{cc}{(B\&C)|((\~B)\&D)~~~~~0\leq t \leq19}\\ {B\oplus C\oplus D~~~~~20\leq t \leq 39}\\ {(B\&C)|(B\&D)|(C\&D)~~~~~40\leq t \leq 59}\\ {B\oplus C\oplus D~~~~~ 60\leq t \leq 79}\end{array} \right.
f(x)=⎩
⎨
⎧(B&C)∣((B˜)&D) 0≤t≤19B⊕C⊕D 20≤t≤39(B&C)∣(B&D)∣(C&D) 40≤t≤59B⊕C⊕D 60≤t≤79
md5初始化数据是4个双字,产生的消息摘要为128位,而sha1初始化数据为5个双字,产生的消息摘要为160位。
将这些信息记下来,就能快速判断程序使用哪种加密函数
分析sha1KeyGenMe.exe
查壳,发现程序使用了sha1加密
运行程序
根据Wrong Serial
字符串在OD中定位到弹出窗口的代码,往上翻,找到程序获取用户输入的API函数,下断点后运行程序。
004014EE |. 83F8 14 cmp eax,0x14 比较获取序列号的长度,如果不是20,验证失败
004014F1 |. 0F85 F3000000 jnz SHA1KeyG.004015EA
先把序列号改成12345678901234567890, 满足长度要求,重新运行程序
接着分析代码
004014F7 |. 8D8424 600300>lea eax,dword ptr ss:[esp+0x360]
004014FE |. 50 push eax
004014FF |. E8 FCFAFFFF call SHA1KeyG.00401000 ; 初始化sha1
00401504 |. 83C4 04 add esp,0x4
00401507 |. 33FF xor edi,edi
00401509 |. 3BF3 cmp esi,ebx
0040150B |. 7E 1E jle short SHA1KeyG.0040152B
0040150D |> 0FBE8C3C D001>/movsx ecx,byte ptr ss:[esp+edi+0x1D0] ; name
00401515 |. 8D9424 600300>|lea edx,dword ptr ss:[esp+0x360]
0040151C |. 51 |push ecx
0040151D |. 52 |push edx
0040151E |. E8 1DFBFFFF |call SHA1KeyG.00401040 ; 每调用一次这个函数[esp+0X360]的值就会改变
00401523 |. 83C4 08 |add esp,0x8
00401526 |. 47 |inc edi
00401527 |. 3BFE |cmp edi,esi
00401529 |.^ 7C E2 \jl short SHA1KeyG.0040150D
0040152B |> 8D8424 080100>lea eax,dword ptr ss:[esp+0x108] ; 缓冲区
00401532 |. 8D8C24 600300>lea ecx,dword ptr ss:[esp+0x360]
00401539 |. 50 push eax
0040153A |. 51 push ecx
0040153B |. E8 60FDFFFF call SHA1KeyG.004012A0 ; 调用sha1
我们不需要通过分析每一个函数的具体汇编代码来理解函数作用,比如上面的代码,只要我们注意到[esp + 0x108]是缓冲区,提前跟踪数据窗口,就能知道004012A0
函数是sha1加密函数
在线网站验证一下上面的数据是不是用户名sha1的结果
继续分析加密代码
0040152B |> \8D8424 080100>lea eax,dword ptr ss:[esp+0x108] ; 缓冲区
00401532 |. 8D8C24 600300>lea ecx,dword ptr ss:[esp+0x360]
00401539 |. 50 push eax
0040153A |. 51 push ecx
0040153B |. E8 60FDFFFF call SHA1KeyG.004012A0 ; 用户名sha1
00401540 |. 83C4 08 add esp,0x8
00401543 |. 33C0 xor eax,eax
00401545 |> 8A5404 34 /mov dl,byte ptr ss:[esp+eax+0x34] ; 50 45 44 49 59 20 46 6F 72 75 6D 00 第一个字节. . . . .
00401549 |. 8A8C04 080100>|mov cl,byte ptr ss:[esp+eax+0x108] ; sha1结果
00401550 |. 32D1 |xor dl,cl
00401552 |. 885404 40 |mov byte ptr ss:[esp+eax+0x40],dl ; 区域1正好在PEDIY Forum结束的后两个字节
00401556 |. 40 |inc eax
00401557 |. 83F8 11 |cmp eax,0x11
0040155A |.^ 7C E9 \jl short SHA1KeyG.00401545
0040155C |. 83F8 14 cmp eax,0x14
0040155F |. 7D 1B jge short SHA1KeyG.0040157C
00401561 |. 8D4C24 28 lea ecx,dword ptr ss:[esp+0x28] ; pediy.com
00401565 |. 83E9 11 sub ecx,0x11
00401568 |> 8A1401 /mov dl,byte ptr ds:[ecx+eax] ; 三次循环 70 65 64
0040156B |. 329404 080100>|xor dl,byte ptr ss:[esp+eax+0x108] ; 从加密后的第十八个字节开始 到第二十个字节结束
00401572 |. 40 |inc eax
00401573 |. 83F8 14 |cmp eax,0x14
00401576 |. 885404 3F |mov byte ptr ss:[esp+eax+0x3F],dl ; 区域2连接在区域1之后,总共改变三字节
0040157A |.^ 7C EC \jl short SHA1KeyG.00401568
0040157C |> 8B1D A4504000 mov ebx,dword ptr ds:[<&USER32.wsprintfA>; user32.wsprintfA
00401582 |. 33F6 xor esi,esi
00401584 |. 8D7C24 10 lea edi,dword ptr ss:[esp+0x10]
00401588 |> 8A4434 4A /mov al,byte ptr ss:[esp+esi+0x4A] ; 第十一个字节开始 (下标为十)
0040158C |. 8A4C34 40 |mov cl,byte ptr ss:[esp+esi+0x40] ; 从第一个字节(下标为0)开始
00401590 |. 32C8 |xor cl,al
00401592 |. 8AC1 |mov al,cl
00401594 |. 884C34 40 |mov byte ptr ss:[esp+esi+0x40],cl ; 改变区域1,从第一个字节开始
00401598 |. 25 FF000000 |and eax,0xFF ; eax % 0x100
0040159D |. 50 |push eax
0040159E |. 68 4C604000 |push SHA1KeyG.0040604C ; ASCII "%02X"
004015A3 |. 57 |push edi ; user32.GetDlgItemTextA
004015A4 |. FFD3 |call ebx
004015A6 |. 83C4 0C |add esp,0xC
004015A9 |. 46 |inc esi
004015AA |. 83C7 02 |add edi,0x2
004015AD |. 83FE 0A |cmp esi,0xA ; 循环十一次
004015B0 |.^ 7C D6 \jl short SHA1KeyG.00401588
尝试写出注册机
import hashlib
name = input("请输入字符串")
data = [0x50, 0x45, 0x44, 0x49, 0x59, 0x20, 0x46, 0x6F, 0x72, 0x75, 0x6D, 0x00]
sha1_result = hashlib.sha1()
sha1_result.update(name.encode())
print("sha1结果是", sha1_result.hexdigest())
for i in range(0 , 0x11):
tmp = data[i] ^ int(sha1_result.hexdigest()[2*i:2*i+2], 16)
data.append(tmp)
data.append(0x70^int(sha1_result.hexdigest()[34:36],16))
data.append(0x65^int(sha1_result.hexdigest()[36:38],16))
data.append(0x64^int(sha1_result.hexdigest()[38:40],16))
data.append(0)
for i in range(11):
data[12 + i] = data[12 + i] ^ data[22 + i]
result = ''
for i in range(12, 22):
result += hex(data[i]).replace('0x','').upper().zfill(2)
print("序列号为",result)
本文结束,接下来的内容是一些废话
后记
我在文章开头说没有密码学基础,但是我上过学校的网络安全知识导论,里面虽然有讲到这些算法,但是老师没有讲明白。
实在不清楚导论课有什么意义,可能起了造型上的作用?
照例的meme时间
这ctf比赛,我有四不打:
- 没有逆向和pwn的比赛我不打,因为它善。出题人花了那么长的时间出水题,我一不小心夸夸夸全做出来了,对不起出题人的辛苦付出
- 有逆向和pwn的比赛我不打,因为它难。长时间分析可执行程序,难免头昏眼花,影响接下来的学习效率
- 线上比赛我不打,线上比赛监管力度不高,通常比赛还没结束,flag就漫天飞了,没什么含金量
- 线下比赛我也不打,因为我是社恐,在一堆参赛选手中进行比赛,容易紧张,发挥不出正常水平
最近的学习内容
-
入坑二进制安全了
-
在做
s.6081
的lab1,有一个bug花费了我一小时,结果发现是头文件顺序写反了
-
在写注册机时,本来想在cpp下使用openssl第三方库。
参考博客
结果花了一天,还是没有成功导入openssl。。。 解决了这个bug,就有其他bug冒出来:(