逆向中常常出現一些加密演算法,如果我們能對這些加密演算法進行快速識別則會大大減少我們逆向的難度,雖然IDA已有密碼分析神器Findcrypt,但掌握手動分析方法能幫助我們應對更多的情況,這篇文章將介紹逆向中常見的單項散列演算法和對稱加密演算法的識別方法,
0xFF. 前言
在很長一段時間里我經常發現自己面對復雜的加密演算法無從下手,可能是因為還沒有系統學過密碼學吧orz,總之這個問題困擾了我很久,于是最近我花了一些時間來解決自己在密碼學這塊的薄弱點,寫下這篇文章的目的之一也是為了鞏固所學知識,
加密演算法的部分沒有涉及公鑰加密演算法(因為我不會hh),每個演算法都有一個例子,這些例子出自《加密與解密(第4版)》第6章的隨書附帶檔案和各大比賽中的題目,
0x00. 目錄
單向散列演算法:
- MD5
- SHA
對稱加密演算法:
- RC4
- TEA
- DES
- AES
- SM4
- ChaCha20
0X01. MD5
MD5(Message Digest Algorithm)訊息摘要演算法對輸入的任意長度的訊息進行運算,產生一個128位的訊息摘要,
MD5的特征是會出現下圖中 A,B,C,D 這四個常量,

這里我們直接用《加密與解密(第4版)》隨書檔案MD5KeyGenMe.exe來分析,

在匯入表中找到GetDlgItemTextA函式定位關鍵代碼,兩次GetDlgItemTextA函式讀取的應該分別是Name和Serial Number:

sub_4012B0函式我們點進去看看發現了MD5的幾個特征常量:

還原一下符號,需要注意的一點是連續呼叫兩次MD5_Update相當于把兩次的輸入拼接后呼叫一次MD5_Update的結果:

寫出注冊機:
from hashlib import md5
name = b'pediy'
digest = md5(name + b'www.pediy.com').digest()
a2345 = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'
serial_number = ''
for b in digest:
serial_number += a2345[b % 32]
print(f'Serial Number('{name}')={serial_number[0:4]}-{serial_number[4:8]}-{serial_number[8:12]}-{serial_number[12:16]}')

Findcrypt插件能幫助我們快速找到這些常量,不過這里還是著重講手動分析的方法,
0x02. SHA
安全散列演算法(Secure Hash Algorithm,SHA)包括SHA-1、SHA-256、SHA-384和SHA-512,分別產生160位、256位、384位和512位的散列值,
類似于MD5,SHA演算法使用了一系列的常數:


還是用隨書檔案SHA1KeyGenMe.exe來分析,
還是先通過匯入表定位關鍵代碼:

發現sub_401000函式中出現了SHA1演算法用到的常量:

還原下符號:

寫出注冊機:
from hashlib import sha1
name = b'pediy'
digest = sha1(name).digest()
aPEDIY = b'PEDIY Forum'
apediy = b'pediy.com'
key = bytearray(digest)
for i in range(11):
key[i] ^= aPEDIY[i]
for i in range(12,17):
key[i] ^= key[i - 12]
for i in range(17,20):
key[i] ^= apediy[i - 17]
serial_number = ''
for i in range(10):
serial_number += hex(key[i] ^ key[i + 10])[2:].zfill(2).upper()
print(serial_number)
0x03. RC4
RC4是一種比較簡單的流密碼,該演算法雖然沒有用到特征常量,但是特征也比較容易識別,

分析RC4 Sample.exe檔案,
還是通過匯入表找到關鍵代碼:

sub_401000函式明顯具有RC4密鑰調度演算法(KSA)的特征:

sub_401070函式是RC4演算法的第二步:

0x04. TEA
TEA演算法是分組密碼,分組長度為64位,密鑰長度為128位,采用Feistel網路,
其加密程序也非常簡單,下面的代碼摘自Wikipedia:
#include <stdint.h>
void encrypt (uint32_t v[2], const uint32_t k[4]) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9E3779B9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
其中特征常量delta是由黃金分割點得來的,delta = 0x9E377989,TEA的變體XTEA和XXTEA都用到了這個常量,但是加密程序不同,在識別演算法時需要注意,
在加密輪數方面,作者推薦的加密輪數是64輪,即回圈32次,也可以采用其他加密輪數,比如32輪或者128輪,在分析的時候也需要注意,
補充Wikipedia上的一些資料:
- Tiny Encryption Algorithm
- XTEA
- XXTEA
分析TEAKeyGenMe.exe:

sub_401380函式中出現了MD5特征常量:

sub_401000函式中出現了TEA特征常量,明顯為TEA演算法:

整個加密程序是先取MD5(name)的前8個位元組作為密鑰對序列號進行TEA加密,加密的結果再與與MD5(name)的前8個位元組異或,異或的結果與MD5的后8個位元組比較,
寫出注冊機:
from hashlib import md5
from binascii import b2a_hex
from pytea import TEA
name = b'pediy'
md5_digest = md5(name).digest()
buf = bytearray(md5_digest[0:8])
for i in range(8):
buf[i] ^= md5_digest[i + 8]
tea = TEA(md5_digest)
print(f'Serial Number("{name.decode()}"): {b2a_hex(tea.Decrypt(buf)).decode().upper()}')
我在網上找了半天也沒找到個好用的TEA的Python實作,干脆自己寫了個:已上傳GitHub
0x05. DES
DES全稱為Data Encryption Standard,即資料加密標準,是一種使用密鑰加密的分組演算法,
DES同前面的TEA一樣,都采用了Feistel網路,其加密程序可以用以下兩個圖表示:
Figure 1— The overall Feistel structure of DES

Figure 2—The Feistel function (F-function) of DES

圖一中的IP和FP分別代表初始置換(Initial Permutation)和末尾置換(Final Permutation),圖二中的S1到S8是8個置換盒(Substitution-Box),這些都可以作為識別DES演算法的特征,
隨便在GitHub上扒一份原始碼,就能找到這些常量:
int S1[] = {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13};
int S2[] = {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9};
int S3[] = {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12};
int S4[] = { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14};
int S5[] = { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3};
int S6[] = {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13};
int S7[] = { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12};
int S8[] = {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11};
在2020祥云杯的某道APK逆向里,Findcrypt插件失效(可能是Findcrypt分析不了ARM框架下的檔案),所以我們只能靠手動分析找到DES的特征(以下是S1到S8):

還有一些別的特征,都可以幫助我們快速識別DES演算法:

0x06. AES
AES(Advanced Encryption Standard,高級加密標準)是用于代替DES的新一代加密標準,AES具有128位元的分組長度,支持128位元、192位元和256位元的密鑰長度,
AES的加密程序:

SubBytes函式:

我們識別AES的方法就是找到AES的SubBytes函式中使用的這個S-box,
在GitHub上扒一份原始碼:
/*
* S-box transformation table
*/
static uint8_t s_box[256] = {
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, // 0
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, // 1
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, // 2
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, // 3
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, // 4
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, // 5
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, // 6
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, // 7
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, // 8
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, // 9
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, // a
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, // b
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, // c
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, // d
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, // e
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};// f
/*
* Inverse S-box transformation table
*/
static uint8_t inv_s_box[256] = {
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, // 0
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, // 1
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, // 2
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, // 3
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, // 4
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, // 5
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, // 6
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, // 7
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, // 8
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, // 9
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, // a
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, // b
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, // c
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, // d
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, // e
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};// f
下面分析AESKeyGenMe.exe檔案,
初步分析關鍵代碼:

在sub_401EC0函式中找到AES的S_box和S_box的逆,基本確定是AES加密,沒有找到iv,推測是ECB模式:

密鑰:

寫出注冊機:
from hashlib import md5
from Crypto.Cipher import AES
from binascii import b2a_hex
name = b'pediy'
md5_digest = md5(name).digest()
aes = AES.new(key=b'\x2B\x7E\x15\x16\x28\xAE\xD2\xA6\xAB\xF7\x15\x88\x09\xCF\x4F\x3C',mode=AES.MODE_ECB)
dec = aes.decrypt(md5_digest)
print(f'Serial Number("{name.decode()}")={b2a_hex(dec).decode().upper()}')
0x07. SM4
SM4是國密演算法,由國家密碼局發布,SM4是一個分組演算法,分組長度為128位元,密鑰長度為128位元,其結構是Fesitel網路的一個變體,
我們識別SM4演算法的方法同樣是找到SM4的S-box(在GitHub上找的原始碼):
static const unsigned char SboxTable[16][16] =
{
{0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05},
{0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99},
{0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62},
{0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6},
{0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8},
{0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35},
{0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87},
{0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e},
{0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1},
{0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3},
{0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f},
{0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51},
{0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8},
{0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0},
{0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84},
{0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48}
};
拿2020縱橫杯的第一道逆向題friendlyRE舉例,
我們直接找到關鍵代碼,這里是比較了Str1和Str2,通過交叉參考可以確定Str2加密程序,Str2為"2NI5JKCBI5Hyva+8AZa3mq!!":

再去找Str1的加密程序,發現有個地方用到了Base64表:

并且表是變換過的,第一次是大小寫互換,第二次相當于是把表的前32位和后32位互換:


繼續找找到了SM4的S-box:

找到key:

整個程序大概就是把輸入經過SM4加密之后再經過一個變表的BASE64再與"2NI5JKCBI5Hyva+8AZa3mq!!"比較,寫出exp:
import base64
from binascii import b2a_hex, a2b_hex
from gmssl.sm4 import CryptSM4, SM4_DECRYPT
BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
BASE64_change = (BASE64[32:] + BASE64[:32]).swapcase()
table = str.maketrans(BASE64_change,BASE64)
Str2 = 'N25IKJBC5IyHav8+ZA3aqm!!'
target = ''
for i in range(0,len(Str2),2):
target += Str2[i + 1]
target += Str2[i]
target = target.translate(table).replace('!','=')
base64_decode = base64.b64decode(target)
print(b2a_hex(base64_decode))
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(b'Thisisinsteresth',SM4_DECRYPT)
flag = crypt_sm4.crypt_ecb(base64_decode)
print(b2a_hex(flag))
理論上是可以這么寫,但是gmssl庫的SM4的padding方式跟題目里SM4的padding方式不一致,導致無法解密,
所以換工具解密:

Hex2Str,得到最后的flag為:DoyouKnowVEHSEH!
0x08. ChaCha20
ChaCha系列流密碼,作為Salsa密碼的改良版,具有更強的抵抗密碼分析攻擊的特性,“20”表示該演算法有20輪的加密計算,
ChaCha20有一個初始矩陣,矩陣的輸入為一個256位的密鑰、64位亂數、64位計數器值以及4×32位的常數,它們均填充在32位整型陣列中作為初始矩陣,排列方式如下:

四個常數0x61707865 0x3320646e 0x79622d32 0x6b206574按小端存盤轉為ASCII字符是"expand 32-byte k",這是我們用來識別ChaCha20演算法的主要特征,
分析今年年初*CTF的一道題Favourite Architecure flag0,riscv架構的檔案只能用Ghirda分析,由于未知原因main函式的反匯編失效了,只能手擼匯編,

我們直接從列印錯誤的代碼開始分析,有兩個大跳轉跳到了這里,往回看能找到能找到兩個地方呼叫的是同一個函式,估計是對加密結果進行比較:


從第一次比較分析,先把用來比較的資料dump下來:
enc1 = b'\x88\xE7\x03\xB4\x36\xCD\x97\xAB\x5A\xA5\xA6\x0B\xDF\xCE\x08\x3B\x9D\x90\x32\x3C\x4E\x15\x14\xBD\x8D\x38\x38\xB0\xEE\x2A\xBC\x4B\xF9\xAA\x24\x26\x76\xA3\xA5\x75\x5E'
從比較函式往前找,找到了這個很詭異的地方:

百度搜索關鍵詞expand 32-byte k找到了GitHub上的一處代碼:

一比對發現是幾乎一模一樣(實際上并不一樣),鎖定第一個加密演算法是ChaCha20:

順藤摸瓜找到密鑰:

然而用了各種Python庫和在線網站都解密不了,一度懷疑人生...
后來分析發現了題目里用到的ChaCha20演算法貌似跟正常的ChaCha20演算法不太一樣,一般來說ChaCha20演算法輸入的Nonce(亂數)是8位元組,題目中的ChaCha20演算法卻給了12個位元組的Nonce,于是改變思路去GitHub上翻了幾個ChaCha20的C語言實作,找到了題目用到的原始碼:




比對下來發現是完全一樣,依葫蘆畫瓢寫出exp的第一部分:
#include "chacha20.h"
#include <stdio.h>
#include <stdlib.h>
int main(){
char enc[100] = "\x88\xE7\x03\xB4\x36\xCD\x97\xAB\x5A\xA5\xA6\x0B\xDF\xCE\x08\x3B\x9D\x90\x32\x3C\x4E\x15\x14\xBD\x8D\x38\x38\xB0\xEE\x2A\xBC\x4B\xF9\xAA\x24\x26\x76\xA3\xA5\x75\x5E";
char key[100] = "tzgkwukglbslrmfjsrwimtwyyrkejqzo";
char nonce[100] = "oaeqjfhclrqk";
struct chacha20_context *ctx = (struct chacha20_context*)malloc(sizeof(struct chacha20_context));
chacha20_init_context(ctx,(uint8_t*)key,(uint8_t*)nonce,0x80);
chacha20_xor(ctx,(uint8_t*)enc,41);
printf(enc);
}
flag{have_you_tried_ghidra9.2_decompiler_
其實更好的方法是直接動態除錯dump密鑰流異或,無奈riscv的動態除錯環境沒搭起來,
然而這種ChaCha20實作到底是變體還是翻車就不知道了...
第二部分則是一個很明顯的TEA:

寫出第二部分的exp:
from pytea import TEA
key = b'\xBB\xA0\x68\x13\x1E\xCE\x0A\x19\x57\xA3\xD8\x35\x61\x2C\xBF\x26'
enc = b'\xF9\x87\x50\xC4\xB2\xF2\x03\x07\x3C\xF4\x74\x69\x59\xBB\xB4\xED\x2A\xB0\xF0\x0F\xF2\x20\x85\x00\xDD\x23\xCD\xFD\x75\x48\x02\x35\xD3\xB6\xD7\xF1\xE1\x1B\xF2\x74\x12\xBF\x2D\xCB\xF6\x53\xB4\xA4'
cipher = TEA(key)
print(cipher.Decrypt(enc,16).decode())
if_you_have_hexriscv_plz_share_it_with_me_thx:P}
完整的flag:
flag{have_you_tried_ghidra9.2_decompiler_if_you_have_hexriscv_plz_share_it_with_me_thx:P}
0x09. 總結
最后再總結一下識別單向散列演算法和對稱加密演算法的方法:
- 直接用IDA的插件Findcrpyt
- RC4、TEA這些流程比較簡單的演算法可以直接通過加密程序識別
- 一些用到了常量的演算法,可以通過它的特征常量識別,比如TEA的delta、ChaCha20的sigma、DES和AES以及SM4的S-Box等等
- 善用GitHub,作為老程式員,這種復雜的加密演算法一般都是在GitHub上抄的(至少我不會自己寫hh)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298292.html
標籤:AI
上一篇:Python大一學習感想
