开始今天的学习吧
对称加密算法
对称加密算法是基于这样的假设:双方使用相同的加密方法和解密方法。
算法的安全性依赖于以下两点
- 加密算法本身足够强:即使攻击者获取到了密文,在没有密钥的情况下,攻击者无法轻易分析出明文
- 密钥的保密性
比较反直觉的是,将加密算法的保密并不一定能使整个系统更安全。因为这个保密的算法没有经过各位逆向高手,数学专家的验证。验证这个算法的唯一方式就是将这个算法公开,让其他的密码员分析
对称加密算法的使用范围很广,比如简单的移位密码和凯撒密码,或者是IDEA,TEA,AES这些比较“高级”的算法
顺便吐槽一下,大一的离散数学实验和计算机导论实验都让我们编程实现凯撒密码。真的太敷衍了。。。。
RC4流密码
这个算法和MD5 , SHA1相比没有很复杂
可以参考这两篇博客学习:
- 对称加密之流密码RC4 这篇csdn博客侧重介绍原理
- RC4加密算法及逆向方法初探 吾爱破解的博客,有例题讲解
打算复现 2018 9月安恒杯的NewDriver
赛题,这是我找到的安恒杯题目仓库Anheng_cup_month , 但是仓库已经不再维护了。
复现安恒杯的NewDriver
题目
参考文章:
- 2018年九月份安恒杯月赛之NewDriver
- 在不知道RC4算法的情况下,只能一步步调试分析,具体可以看这篇吾爱破解的文章:九月份安恒杯月赛之NewDriver
ida打开main函数
这个题目有三个关键调用sub_401160
, sub_401000
, sub_4010E0
。
sub_401000
, sub_4010E0
函数有很明显的rc4特征(废话人家就是rc4算法),我们可以依靠这些特征快速识别rc4:
- 生成s盒时,两个循环都进行了256次
- 很多语句会mod256
下图是sub_4010E0
函数和《加密与解密》中PRGA的对比,可以发现结构一样,进一步验证了这两个函数是rc4加密函数的猜想
接着分析sub_401160
函数,这段算法的特征也很明显:
- 将3个字节分成4组,每一组6个二进制位。 将6个二进制位前面填充0,得到8个二进制位,作为替换表的下标
byte_402138
是替换表,有64个字符
所以这是更改了替换表的base64加密函数
考虑写出注册机,需要手写rc4函数,第一步就是分析在ida中,表示s盒和t盒的变量分别是谁,所以分析sub_401000
函数
-v4
表示i
, a3
表示key的长度
v6[i]
指的就是a1[v4]
, 程序使用0-255初始化a1
数组,所以它就是s盒v6
是t盒,因为只有t盒需要用到i%keylength
。动态调试发现它的值就是假的flagflag{this_is_not_the_flag_hahaha}
其实我们可以直接用动态调试找到KSA
变换后的s盒a1
, 这么做就不用通过t盒手动计算变换后s盒的值了。接下来仿照第三个加密函数,写出脚本
key=[0x66, 0x32, 0xCA, 0xA0, 0xBF, 0x98, 0x2D, 0x76, 0xF1, 0x59, 0x2A, 0x4A, 0xF4, 0x30, 0xAD, 0xD2, 0x1D, 0x02, 0xD8, 0x23, 0x89, 0x5D, 0x83, 0x38, 0x09, 0xF2, 0x74, 0x65, 0x40, 0x19, 0xC6, 0xDD, 0x18, 0xD3, 0x8F, 0x6C, 0x8B, 0xC0, 0xC5, 0x54, 0x2E, 0x81, 0x10, 0xC4, 0x26, 0x56, 0x5F, 0x53, 0x80, 0x43, 0x27, 0x62, 0xEA, 0x3D, 0xE6, 0x00, 0xE7, 0xB7, 0x50, 0x94, 0x90, 0x4C, 0x3F, 0x9D, 0x07, 0xE0, 0xA3, 0x9C, 0x4E, 0x0F, 0x9F, 0xFE, 0x5B, 0x8E, 0xDE, 0x88, 0x72, 0x2F, 0xC1, 0x67, 0x31, 0x70, 0x8D, 0xFD, 0xBE, 0x64, 0xC3, 0xBD, 0x6B, 0x7A, 0xCF, 0x0C, 0x34, 0x1F, 0x6F, 0x01, 0xF0, 0x7C, 0x5E, 0xA4, 0x1E, 0x49, 0x8C, 0x75, 0x1C, 0xE3, 0x20, 0x48, 0x28, 0x79, 0xA5, 0x7F, 0xF5, 0xEC, 0x4F, 0x78, 0x58, 0x11, 0xF7, 0xCD, 0x91, 0x13, 0xFC, 0xB8, 0x2C, 0x04, 0xEE, 0xD5, 0x08, 0x44, 0xA9, 0xE1, 0xB1, 0x42, 0x84, 0x29, 0xA7, 0x47, 0x97, 0x7E, 0xE8, 0xB3, 0x60, 0x0B, 0xF9, 0x4B, 0x3C, 0x77, 0x17, 0x03, 0x82, 0x69, 0x87, 0xD4, 0x95, 0x1A, 0x33, 0x25, 0x6E, 0xCC, 0xD6, 0xBB, 0x99, 0xB0, 0x85, 0x41, 0xB2, 0x0D, 0xDB, 0x35, 0x3B, 0x5C, 0xF8, 0xED, 0x9E, 0xA6, 0x96, 0x39, 0x63, 0x0A, 0x1B, 0x93, 0x21, 0x46, 0x12, 0xD0, 0xB4, 0x22, 0x51, 0xC9, 0x61, 0xD1, 0x2B, 0xAA, 0x45, 0x06, 0x05, 0xCE, 0xFA, 0x92, 0x68, 0xAB, 0x36, 0xDA, 0xC8, 0xE2, 0x37, 0xD9, 0xA2, 0x5A, 0xD7, 0x6A, 0xB5, 0xFF, 0xE9, 0xBA, 0x52, 0x15, 0xF6, 0xBC, 0x9A, 0xB6, 0xEF, 0x6D, 0xCB, 0x4D, 0xAE, 0xE4, 0xA1, 0xAC, 0xEB, 0x0E, 0x71, 0x7B, 0xF3, 0x24, 0xC2, 0xFB, 0x7D, 0x86, 0x55, 0xAF, 0x3A, 0xDF, 0x3E, 0x14, 0xB9, 0x9B, 0x16, 0xDC, 0x73, 0x57, 0xE5, 0xC7, 0x8A, 0xA8, 0x66, 0x6C, 0x61, 0x67, 0x7B, 0x74, 0x68, 0x69, 0x73, 0x5F, 0x69, 0x73, 0x5F, 0x6E, 0x6F, 0x74, 0x5F, 0x74, 0x68, 0x65, 0x5F, 0x66, 0x6C, 0x61, 0x67, 0x5F, 0x68, 0x61, 0x68, 0x61, 0x68, 0x61, 0x7D]
key2=[0x20, 0xC3, 0x1A, 0xAE, 0x97, 0x3C, 0x7A, 0x41, 0xDE, 0xF6, 0x78, 0x15, 0xCB, 0x4B, 0x4C, 0xDC, 0x26, 0x55, 0x8B, 0x55, 0xE5, 0xE9, 0x55, 0x75, 0x40, 0x3D, 0x82, 0x13, 0xA5, 0x60, 0x13, 0x3B, 0xF5, 0xD8, 0x19, 0x0E, 0x47, 0xCF, 0x5F, 0x5E, 0xDE, 0x9D, 0x14, 0xBD]
key3=[]
v3=0
v4=0
v6=0
s=""
#这里直接仿写IDA中第三个加密函数逻辑即可
for v3 in range(44):
v3+=1
v6=key[v3]
v4=(v6+v4)%256
key[v3]=key[v4]
key[v4]=v6
key3.append(key[(key[v3]+v6)%256])
for i in range(44):
key2[i]^=key3[i]
for i in key2:
s+=chr(i)
print s
我直接使用了其他人的exp,下面是解释:
这就是下面的代码中,result值的来源:由eax寄存器传递。我们最终需要求出a2[i]
的值,也就是经过魔改的base64加密后的值。
通过上面的python脚本,算出值为ZeptZ3l5UHQra25nd19yYzMrYR5wX2Jtc2P2VF9gYNM9
在在线网站上解密,得到flag
flag{y0u_know_rc4_and_base64_ha$}
tea算法
这就是tea算法的具体代码
#define DELTA 0x9981abcd
void tea_encrypt(unsigned int* v, unsigned int* key) {
unsigned int y = v[0], z = v[1], sum = 0;
for (size_t i = 0; i < 32; i++) {
y += ((z<<4)+key[0])^(z+sum) ^((z>>5) + key[1]);
sum += DELTA; //累加Delta的值
z += ((y << 4) + key[2]) ^ (sum +y)+((y>>5)+key[3]); //
}
v[0] = y;
v[1] = z;
}
void tea_decrypt(unsigned int* v, unsigned int* key) {
unsigned int y = v[0], z = v[1], sum = 0;
sum = DELTA * 32; //32次迭代累加后delta的值
for (size_t i = 0; i < 32; i++) {
z -=((y<<4) + key[2])^(y+sum)^((y>>5) + key[3]);
sum -= DELTA;
y -= ((z<<4)+key[0])^(z+sum)^((z>>5)+key[1]);
}
v[0] = y;
v[1] = z;
}
tea算法的特点:
- 密钥长度为128位,分为四组每一组32位参与运算
- 明文64位,分成32位的两部分参与运算
按照ctf wiki的推荐,我找到了2018 0ctf的Quals milk-tea题目,虽然尝试复现,但我对windows编程了解不够深入,最终没成功分析明白。分析过程我放在后记里了。
这是我找到的放ctf赛题的github仓库CTF Challenges
这是找到的wp 0CTF Quals 2018 Writeup
分析TeaKeyGenMe程序
程序使用MD5加密
一样通过抓关键特征 + 线上验证的方式分析MD5算法
00401276 |. 8B4C24 2C mov ecx,dword ptr ss:[esp+0x2C] ; 低64位的密码
0040127A |. 8B5424 30 mov edx,dword ptr ss:[esp+0x30] ; 高64位的密码
0040127E |. 8D8424 BC0100>lea eax,dword ptr ss:[esp+0x1BC]
00401285 |. 894C24 10 mov dword ptr ss:[esp+0x10],ecx
00401289 |. 50 push eax ; 这里是缓冲区
0040128A |. 895424 18 mov dword ptr ss:[esp+0x18],edx
0040128E |. E8 ED000000 call TEAKeyGe.00401380 ; 初始化md5
00401293 |. 8B4C24 2C mov ecx,dword ptr ss:[esp+0x2C] ; 用户名字符个数
00401297 |. 8D9424 E00200>lea edx,dword ptr ss:[esp+0x2E0] ; 用户名
0040129E |. 51 push ecx
0040129F |. 8D8424 C40100>lea eax,dword ptr ss:[esp+0x1C4] ; 密码
004012A6 |. 52 push edx
004012A7 |. 50 push eax
004012A8 |. E8 03010000 call TEAKeyGe.004013B0 ; 密码的后一个字节放用户名长度*8 , 后9个字节放用户名
004012AD |. 8D8C24 CC0100>lea ecx,dword ptr ss:[esp+0x1CC] ; 上一个函数的结果
004012B4 |. 8D9424 040100>lea edx,dword ptr ss:[esp+0x104] ; 这应该是缓冲区
004012BB |. 51 push ecx ; 19f5f4
004012BC |. 52 push edx
004012BD |. E8 9E010000 call TEAKeyGe.00401460 ; 对用户名进行md5加密
004012C2 |. 8B8C24 100100>mov ecx,dword ptr ss:[esp+0x110]
当输入用户名为abcdef
,并且调用401460
函数后,加密结果如下图
继续分析
004012EE |. 8D5424 28 lea edx,dword ptr ss:[esp+0x28]
004012F2 |. 51 push ecx ; 用户名进行md5加密的结果,并去掉最后32位
004012F3 |. 52 push edx ; 密码+用户名进行MD5加密的前96位, 一共160位
004012F4 |. 894424 44 mov dword ptr ss:[esp+0x44],eax
004012F8 |. E8 03FDFFFF call TEAKeyGe.00401000
004012FD |. 8B4424 30 mov eax,dword ptr ss:[esp+0x30]
{
00401000 /$ 83EC 0C sub esp,0xC
00401003 |. 53 push ebx
00401004 |. 55 push ebp
00401005 |. 56 push esi
00401006 |. 8B7424 20 mov esi,dword ptr ss:[esp+0x20]
0040100A |. 8B4C24 1C mov ecx,dword ptr ss:[esp+0x1C]
0040100E |. 57 push edi
0040100F |. 8B7E 04 mov edi,dword ptr ds:[esi+0x4]
00401012 |. 33D2 xor edx,edx
00401014 |. 8B01 mov eax,dword ptr ds:[ecx] ; eax为前32位的序列号
00401016 |. 8B49 04 mov ecx,dword ptr ds:[ecx+0x4] ; ecx为后32位的序列号
00401019 |. 897C24 10 mov dword ptr ss:[esp+0x10],edi ; 待加密的信息是序列号 密钥为md5后的用户名
0040101D |. 8B3E mov edi,dword ptr ds:[esi]
0040101F |. 897C24 24 mov dword ptr ss:[esp+0x24],edi
00401023 |. 8B7E 0C mov edi,dword ptr ds:[esi+0xC]
00401026 |. 8B76 08 mov esi,dword ptr ds:[esi+0x8]
00401029 |. 897C24 18 mov dword ptr ss:[esp+0x18],edi
0040102D |. 897424 14 mov dword ptr ss:[esp+0x14],esi
00401031 |. BF 20000000 mov edi,0x20 ; edi= 32
00401036 |> 8B5C24 24 /mov ebx,dword ptr ss:[esp+0x24]
0040103A |. 8B6C24 10 |mov ebp,dword ptr ss:[esp+0x10]
0040103E |. 8BF1 |mov esi,ecx
00401040 |. 81EA 4786C861 |sub edx,0x61C88647 ; 密钥调度常数
00401046 |. C1E6 04 |shl esi,0x4
00401049 |. 03F3 |add esi,ebx
0040104B |. 8BD9 |mov ebx,ecx
0040104D |. C1EB 05 |shr ebx,0x5
00401050 |. 03DD |add ebx,ebp
00401052 |. 8B6C24 18 |mov ebp,dword ptr ss:[esp+0x18] ; TEAKeyGe.004012FD
00401056 |. 33F3 |xor esi,ebx
00401058 |. 8D1C0A |lea ebx,dword ptr ds:[edx+ecx]
0040105B |. 33F3 |xor esi,ebx
0040105D |. 8B5C24 14 |mov ebx,dword ptr ss:[esp+0x14]
00401061 |. 03C6 |add eax,esi
00401063 |. 8BF0 |mov esi,eax
00401065 |. C1E6 04 |shl esi,0x4
00401068 |. 03F3 |add esi,ebx
0040106A |. 8BD8 |mov ebx,eax
0040106C |. C1EB 05 |shr ebx,0x5
0040106F |. 03DD |add ebx,ebp
00401071 |. 33F3 |xor esi,ebx
00401073 |. 8D1C02 |lea ebx,dword ptr ds:[edx+eax]
00401076 |. 33F3 |xor esi,ebx
00401078 |. 03CE |add ecx,esi
0040107A |. 4F |dec edi
0040107B |.^ 75 B9 \jnz short TEAKeyGe.00401036
0040107D |. 8B5424 20 mov edx,dword ptr ss:[esp+0x20]
00401081 |. 5F pop edi
00401082 |. 5E pop esi
00401083 |. 5D pop ebp
00401084 |. 8902 mov dword ptr ds:[edx],eax
00401086 |. 894A 04 mov dword ptr ds:[edx+0x4],ecx
00401089 |. 5B pop ebx
0040108A |. 83C4 0C add esp,0xC
0040108D \. C3 retn
}
可以从以下几个特征识别401000
函数为tea加密函数
00401040 |. 81EA 4786C861 |sub edx,0x61C88647
执行完此处代码,edx的值为0x9e3779b7- 循环执行32次 , 每一次循环esi左移4位,ebx右移5位
如果担心出题人魔改tea,那还是仔细分析每一句代码吧 😦
识别出tea算法还不够,需要找到待加密的数据和密钥数据。根据明文是64位,密钥是128位的规则,很容易识别出待加密的数据为用户输入的序列号,密钥为经过md5加密后的用户名
为什么输入的字符有16个,而最终的位数只有64位呢。原因就是程序使用一个字节存下两个字符,
4011FE
之前的代码保证序列号是十六进制的字符,因此不用考虑放不下的问题
004012FD |. 8B4424 30 mov eax,dword ptr ss:[esp+0x30] ; tea加密结果
00401301 |. 8B4C24 34 mov ecx,dword ptr ss:[esp+0x34]
00401305 |. 83C4 20 add esp,0x20
00401308 |. 894424 2C mov dword ptr ss:[esp+0x2C],eax ; 用户名md5结果
0040130C |. 894C24 30 mov dword ptr ss:[esp+0x30],ecx
00401310 |. 33C0 xor eax,eax
00401312 |> 8A9404 F40000>/mov dl,byte ptr ss:[esp+eax+0xF4]
00401319 |. 8A5C04 2C |mov bl,byte ptr ss:[esp+eax+0x2C]
0040131D |. 32DA |xor bl,dl ; tea加密结果与用户名MD5结果前64位异或
0040131F |. 885C04 2C |mov byte ptr ss:[esp+eax+0x2C],bl
00401323 |. 40 |inc eax
00401324 |. 83F8 08 |cmp eax,0x8
00401327 |.^ 7C E9 \jl short TEAKeyGe.00401312
00401329 |. 8D4424 2C lea eax,dword ptr ss:[esp+0x2C]
0040132D |. 8D8C24 FC0000>lea ecx,dword ptr ss:[esp+0xFC] ; 用户名md5后64位
00401334 |. 50 push eax ; /String2 = 66DBEC8D ???
00401335 |. 51 push ecx ; |String1 = C5F56009 ???
00401336 |. FF15 00704000 call dword ptr ds:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
总结一下程序加密的流程:用户输入用户名和序列号。用户名进行MD5哈希,哈希结果作为tea算法的密钥,用户输入的序列号作为tea算法待加密的数据。tea算法得到的结果与MD5哈希值的前64位数据异或,接着与MD5哈希值的后64位比较,如果不相等,则验证失败。
尝试写出注册机。虽然结果是错的,但我找不出问题出在哪里 😭 😭 😭 😭 😭 😭 😭
import hashlib
from ctypes import *
def encrypt(v, k):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
sum1 = c_uint32(0)
delta = 0x9e3779b9
for i in range(32):
sum1.value += delta
v0.value += ((v1.value << 4) + k[0]) ^ (v1.value + sum1.value) ^ ((v1.value >> 5) + k[1])
v1.value += ((v0.value << 4) + k[2]) ^ (v0.value + sum1.value) ^ ((v0.value >> 5) + k[3])
return v0.value, v1.value
def decrypt(v, k):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
delta = 0x9e3779b9
sum1 = c_uint32(delta << 5)
for i in range(32):
v1.value -= ((v0.value << 4) + k[2]) ^ (v0.value + sum1.value) ^ ((v0.value >> 5) + k[3])
v0.value -= ((v1.value << 4) + k[0]) ^ (v1.value + sum1.value) ^ ((v1.value >> 5) + k[1])
sum1.value -= delta
return v0.value, v1.value
name = input("请输入用户名: ")
hash_result = hashlib.md5(name.encode()).digest()
tea_result = [int.from_bytes(hash_result[:4], 'little'), int.from_bytes(hash_result[4:8], 'little')]
key_list = [int.from_bytes(hash_result[i:i+4], 'little') for i in range(8, 24, 4)]
result = decrypt(tea_result, key_list)
print(''.join([f'{x:08x}' for x in result]))
本文结束
后记
照例meme时间
某些网站上,盗录综艺的画质be like:
在做不出ctf题目时,可以做出如下动作,给自己加油打气
虽然解决不了问题,但可以让队友看笑话啊
参考资料:《是!尚先生》
尝试分析mike tea的过程
如果v10
数组 的值等于v11
数组的值,验证成功。往上翻代码,发现v10
就是我们输入的Str
, 但是暂时没有找到对Str
进行加密的代码
为了判断Str
有没有被加密过,我尝试使用ida进行动态调试,但是报错“请求的操作需要提升”,所以我选择od进行调试
首先要在od中定位这条代码mov ecx , offset Str
。ida的起始地址为00401000 , OD的起始地址为00A61000 。od上述代码对应地址为004019B0 - 00401000 + 00A61000 = 00A619B0
00A6683C
存放我们输入milktea coupon的值,以unicode的形式存放。程序调用A611F0
函数后,该内存发生了变化。
继续调试,发现调用ReadFile
函数导致该内存改变,找到这段代码
从FileA文件中读取的数据被放在缓冲区中。那FileA文件里面的数据是从哪里来的呢?好像是管道的数据,那管道的数据从何而来。。。不懂了
扯一下最近的学习情况
- 破解了两个
creakme
程序 - 浅浅学习了栈溢出漏洞
- 做一题ctf逆向题,里面用到了C#的加密函数,还需要修改PE文件头,识别AES加密函数。被虐后还是滚回来继续看《加密与解密》了:(