# 2022DASCTF X SU 三月春季挑战赛
# login
玩逆向有史以来做的最难的一道题
主要知识点 Socket 通信 RSA 加密 HILL 加密 魔改 AES 加密
[附件下载](链接:https://pan.baidu.com/s/1_zUg4ZrYmB1hPbHJbC4s1Q?pwd=1w3e
提取码:1w3e
-- 来自百度网盘超级会员 V2 的分享)
# 查壳
无壳 64 位,拖进 IDA
# Socket
拖进 IDA,
根据经验可以判断这两个函数分别为 exit () 和 printf ()
观察其他函数,点进去发现
发现很多特殊的函数 sys_* , 根据经验可以判断该种函数为系统函数,不可能是作者自己编写的
可以尝试搜索一下,发现是 Socket 函数
Socket 技术详解 Socket 相关函数
参考完上面的两篇博文就可以发现很多未解析的函数都是 Socket 函数
之后继续对 IDA 未解析的函数进行修复,得到最终的反汇编代码
还有一个细节,以 socket_bind 函数为例
socket 是通过套接字传递信息,而且重要的 ip 地址以及端口信息都保存在关键的结构体中,以便确定与之通信的计算机
这里的 sockaddr 就是保存信息的结构体,但是 IDA 并没有将它解析出来,我们手动添加结构体 socketaddr_in 结构体
具体操作 shift+F9
右键 add 结构体
添加之后,找到 bind () 函数,将第二个参数解析为对应的结构体
解析成功之后就可以看到关键的信息
能够看到 Socket 通信的端口 1234
# 实现逻辑
前一个阶段找到了客户端和服务端通信的端口 1234,由于客户端和服务端都运行在本地,所以 IP 地址也就是
127.0.0.1 本机地址
尝试在 1234 端口下开启单步调试
利用端口 1234 开始单步调试
这里提示需要输入参数 IP 地址才能运行 login
大致的逻辑就是 login 运行后,服务端将字符串发送到客户端,提醒用户输入 token 和 password
接收到用户的输入之后,再将得到的数据发送到服务端进行对比识别
进入到关键的对比函数
刚开始是不知道 RSA 加密以及 HILL 加密的,进入这两个函数分析
# RSA
这里发现了特殊值,直接谷歌搜索 0x10001
就能知道 0x10001 是 RSA 加密的公钥,后面的函数
是将一个字符串转换成 16 进制数,当做私钥
而前面的一个很大的数就是 RSA 中需要因式分解的大质数
通过 yafu 工具或者在线因式分解
import gmpy2 | |
p = 98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033 | |
q = 133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513 | |
n = p * q | |
e = 0x10001 | |
c = b'By reading we enrich the mind, by conversation we polish it.' | |
c = int.from_bytes(c, 'little') | |
#这里要多试一下,确定是大端序还是小端序 | |
d = gmpy2.invert(e, (p - 1) * (q - 1)) | |
m = gmpy2.powmod(c, d, n) | |
print(m) | |
#11963777321199993924175387978397443935563034091716786597947508874393819454915798980986262132792605021295930274531653741552766395859285325677395421549163602968276475448835066393456449574469736327622969755801884982386140722904578598391534204834007447860153096480268812700725451958035204357033892179559153729604237187552716580637492579876006993181920209114166153317182827927606249871955662032809256743464460825303610341043145126848787575238499023185150429072724679210155061579052743238859739734301162335989939278904459012917375108407803445722785027315562371588439877746983153339473213449448259686486917983129418859935686 |
# HILL
刚开始不知道 HILL 加密,进入到函数里面
还有那个 rand () 函数,之前是不知道 rand (), 也是看了 p 师傅的讲解才知道,当然,rand () 函数一般为了限定随机数的范围都是要 % 某个值,所以也能猜到这个函数是 rand (),还有一个要注意的点就是该函数是在 Linux 环境下生成,所以编写脚本的时候也要对应的在 Linux 环境下
所以这个 rand () 产生的数就是我们的密文,%257 和 %255 只是为了确保两个值在同一个范围内
逻辑就理的很清楚,矩阵运算之后与密文比较
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
//Linux 环境下运行 | |
int main(void) | |
{ | |
int i; | |
unsigned char hillData[] = | |
{ | |
81, 50, 210, 2, 195, 45, 149, 185, 249, 120, 213, 20, 227, 41, 66, 32, 81, 59, 21, 98, 52, 130, 180, 192, 46, 154, 253, 232, 186, 213, 236, 7, 72, 106, 84, 136 | |
}; | |
unsigned char rsaData[] = "11963777321199993924175387978397443935563034091716786597947508874393819454915798980986262132792605021295930274531653741552766395859285325677395421549163602968276475448835066393456449574469736327622969755801884982386140722904578598391534204834007447860153096480268812700725451958035204357033892179559153729604237187552716580637492579876006993181920209114166153317182827927606249871955662032809256743464460825303610341043145126848787575238499023185150429072724679210155061579052743238859739734301162335989939278904459012917375108407803445722785027315562371588439877746983153339473213449448259686486917983129418859935686"; | |
unsigned char ivk[48] = { 0 }; | |
for ( i = 0; i < 36; i++ ) | |
printf("%d, ", rand() % 255); | |
puts("\n"); | |
int v5; | |
for ( i = 0; i <= 47; ++i ) | |
{ | |
v5 = rand(); | |
if ( (i & 1) != 0 ) | |
{ | |
ivk[i] = rsaData[v5 % (sizeof(rsaData) / sizeof(unsigned char) - 1)]; | |
} | |
else | |
ivk[i] = hillData[v5 % (sizeof(hillData) / sizeof(unsigned char))]; | |
printf("%d, ", ivk[i]); | |
} | |
return 0; | |
} |
直接逆运算就是 密钥矩阵先取逆然后与密文相乘
就可以自己先写一个 rand () 生成密文,然后利用在线矩阵计算工具
x = Matrix(GF(257), [[113, 219, 37, 46, 122, 15], [76, 163, 106, 34, 170, 41], [110, 27, 169, 122, 138, 39], [47, 128, 240, 14, 170, 86], [247, 89, 88, 0, 169, 242], [246, 154, 78, 28, 72, 201]]) | |
enc = Matrix(GF(257), [[163, 151, 162, 85, 83, 190], [241, 252, 249, 121, 107, 82], [20, 19, 233, 226, 45, 81], [142, 31, 86, 8, 87, 39], [167, 5, 212, 208, 82, 130], [119, 117, 27, 153, 74, 237]]) | |
flag = x.inverse()*enc | |
print(flag) |
得到
再转成 16 进制得到密码
5132d202c32d95b9f978d514e3294220513b15623482b4c02e9afde8bad5ec07486a5488
得到 token 和 password 之后再运行程序测试一下
发现 token 和 password 都是正确的
接着分析下面的加密
# 魔改 AES
首先分析一个函数,该函数的作用即为将数组的两个元素合并成一个元素
然后我们继续查看客户端传入到服务端的数据并把他们两两合并成一个新的数组
我们直接搜索 52 09 6a d5 30
发现就是 AES 加密
AES 加密详解
后面继续分析
发现又有一个 rand () 函数,而且经过实验,rand 函数在同一个程序里面即使重新调用,之前的值也会保留,也就是说,这次 rand 生成的值还是得在 rand 生成值 36 位之后找,所以最后测试密文的时候前面还是要加循环将前 36 个 rand 值踢掉
这个循环体还有一个点就是
他会在第 16 位时进入另一个分支
也就是说,前 16 位是用的 key 后面 36 位是 iv (这也是 AES 的特点)
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
int main(void) | |
{ | |
int i; | |
unsigned char hillData[] = | |
{ | |
81, 50, 210, 2, 195, 45, 149, 185, 249, 120, 213, 20, 227, 41, 66, 32, 81, 59, 21, 98, 52, 130, 180, 192, 46, 154, 253, 232, 186, 213, 236, 7, 72, 106, 84, 136 | |
}; | |
unsigned char rsaData[] = "11963777321199993924175387978397443935563034091716786597947508874393819454915798980986262132792605021295930274531653741552766395859285325677395421549163602968276475448835066393456449574469736327622969755801884982386140722904578598391534204834007447860153096480268812700725451958035204357033892179559153729604237187552716580637492579876006993181920209114166153317182827927606249871955662032809256743464460825303610341043145126848787575238499023185150429072724679210155061579052743238859739734301162335989939278904459012917375108407803445722785027315562371588439877746983153339473213449448259686486917983129418859935686"; | |
unsigned char ivk[48] = { 0 }; | |
for ( i = 0; i < 36; i++ ) | |
printf("%d, ", rand() % 255); | |
puts("\n"); | |
int v5; | |
for ( i = 0; i <= 47; ++i ) | |
{ | |
v5 = rand(); | |
if ( (i & 1) != 0 ) | |
{ | |
ivk[i] = rsaData[v5 % (sizeof(rsaData) / sizeof(unsigned char) - 1)]; | |
} | |
else | |
ivk[i] = hillData[v5 % (sizeof(hillData) / sizeof(unsigned char))]; | |
printf("%d, ", ivk[i]); | |
} | |
return 0; | |
} |
后面就可以继续判断 AES 了
本菜鸡复现的时候也看不懂 AES,只好先挖个坑
看 P 师傅的博客之后才知道了这道题目的魔改
简单介绍一下到底魔改了什么
- S(加密盒)和 Re(解密盒)互换,也就是加密的时候用了解密盒,解密的时候用了加密盒
- 密钥扩展改变了 Rcon 轮常量值
- 再进入加密器前进行了一轮 iv 的异或(像是 ECB 模式前加了轮异或)
PS: 最大的改动是全部都是 8 位运算!!(而 AES 加密是 32 位运算,所以需要自己重写一下)
(个人感觉是如果不自己打遍 AES,审起来会比较累,同时 AES 是经常用的学习一番也是十分不错的)
最后贴上 P 师傅的解题脚本,等我学完 AES 就回来自己写一个(逃
#include <stdio.h>
static const int S[256] ={
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; const unsigned char Rcon[11] ={
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; static const int deColM[4][4] ={
0xE, 0xB, 0xD, 0x9, 0x9, 0xE, 0xB, 0xD, 0xD, 0x9, 0xE, 0xB, 0xB, 0xD, 0x9, 0xE }; static unsigned char W[44]; void ExtendKey(unsigned char * key); void AddRoundKey(unsigned char (*stateMatrix)[4], unsigned char * key); void DeShiftRows(unsigned char (*stateMatrix)[4]); void DeSubBytes(unsigned char (*stateMatrix)[4]); void DeMixColumns(unsigned char (*stateMatrix)[4]); void XorIv(unsigned char * plainText, unsigned char * iv); void AESDecode(unsigned char * enc, unsigned char * plainText, unsigned char * key); int main(void){
unsigned char key[] = {50, 48, 7, 54, 106, 55, 120, 49, 72, 57, 66, 57, 20, 49, 213, 50}; unsigned char iv[] = {98, 54, 249, 56, 66, 48, 195, 49, 106, 53, 72, 56, 52, 53, 84, 52, 41, 52, 81, 54, 21, 57, 210, 56, 210, 57, 32, 49, 185, 50, 46, 48}; unsigned char enc[] = {254, 249, 231, 62, 246, 161, 35, 204, 87, 97, 193, 21, 119, 251, 156, 187, 202, 47, 177, 232, 79, 217, 7, 216, 12, 107, 234, 207, 232, 66, 162, 250}; unsigned char plainText[32] = { 0 }; int i; for ( i = 0; i < 32; i += 16){
AESDecode(enc + i, plainText + i, key); XorIv(plainText + i, iv + i);}
for ( i = 0; i < 32; i++ ) printf("%c", plainText[i]); return 0;}
void XorIv(unsigned char * plainText, unsigned char * iv){
int i; for ( i = 0; i < 16 ; i++ ) plainText[i] ^= iv[i];}
void GetStateMatrix(unsigned char (*stateMatrix)[4], unsigned char * enc){
int i, j; for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) stateMatrix[j][i] = enc[i * 4 + j];}
void PutStateMatrix(unsigned char * plainText, unsigned char (*stateMatrix)[4]){
int i, j; for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) plainText[i * 4 + j] = stateMatrix[j][i];}
void AESDecode(unsigned char * enc, unsigned char * plainText, unsigned char * key){
int i, j; ExtendKey(key); unsigned char stateMatrix[4][4] = { 0 }; GetStateMatrix(stateMatrix, enc); i = 10; AddRoundKey(stateMatrix, W + i * 16); for ( i--; ; i-- ){
DeShiftRows(stateMatrix); DeSubBytes(stateMatrix); AddRoundKey(stateMatrix, W + i * 16); if ( i == 0 ) break; DeMixColumns(stateMatrix);}
PutStateMatrix(plainText, stateMatrix);}
static int GFMul2(int s){
int result = s << 1; int a7 = result & 0x00000100; // 判断位移后的那位是否为 1 if ( a7 != 0 ){
result = result & 0x000000FF; result = result ^ 0x1B; // 矩阵乘法的特殊性}
return result;}
static int GFMul3(int s){
return GFMul2(s) ^ s;}
static int GFMul4(int s){
return GFMul2(GFMul2(s));}
static int GFMul8(int s){
return GFMul2(GFMul4(s));}
static int GFMul9(int s){
return GFMul8(s) ^ s;}
static int GFMul11(int s){
return GFMul9(s) ^ GFMul2(s);}
static int GFMul12(int s){
return GFMul8(s) ^ GFMul4(s);}
static int GFMul13(int s){
return GFMul12(s) ^ s;}
static int GFMul14(int s){
return GFMul12(s) ^ GFMul2(s);}
/**
* GF 上的二元运算 */ static int GFMul(int n, int s){
int result; if ( n == 1 ) result = s; else if ( n == 2 ) result = GFMul2(s); else if ( n == 3 ) result = GFMul3(s); else if ( n == 9 ) result = GFMul9(s); else if ( n == 0xB ) result = GFMul11(s); else if ( n == 0xD ) result = GFMul13(s); else if ( n == 0xE ) result = GFMul14(s); return result;}
void DeMixColumns(unsigned char (*stateMatrix)[4]){
unsigned char tmpArray[4][4]; int i, j; for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) tmpArray[i][j] = stateMatrix[i][j]; for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) stateMatrix[i][j] = GFMul(deColM[i][0], (int)tmpArray[0][j]) ^ GFMul(deColM[i][1], (int)tmpArray[1][j]) ^ GFMul(deColM[i][2], (int)tmpArray[2][j]) ^ GFMul(deColM[i][3], (int)tmpArray[3][j]);}
void DeSubBytes(unsigned char (*stateMatrix)[4]){
int i, j; for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) stateMatrix[i][j] = S[stateMatrix[i][j]];}
void DeShiftRows(unsigned char (*stateMatrix)[4]){
int i, j, count, t; for ( i = 1; i <= 3; i++ ){
count = 0; while ( count++ < i ){
t = stateMatrix[i][3]; for ( j = 3; j >= 1; j-- ) stateMatrix[i][j] = stateMatrix[i][j - 1]; stateMatrix[i][0] = t;}
}
}
void AddRoundKey(unsigned char (*stateMatrix)[4], unsigned char * key){
int i, j; for ( i = 0; i <= 3; i++ ) for ( j = 0; j <= 3; j++ ) stateMatrix[i][j] ^= key[4 * j + i];}
void ExtendKey(unsigned char * key){
unsigned char tmp[16]; int i, j; for ( i = 0; i <= 3; i++ ){
W[4 * i] = key[4 * i]; W[4 * i + 1] = key[4 * i + 1]; W[4 * i + 2] = key[4 * i + 2]; W[4 * i + 3] = key[4 * i + 3];}
for ( j = 4; j < 44; j++ ){
unsigned char v9 = W[4 * (j - 1)]; unsigned char v10 = W[4 * (j - 1) + 1]; unsigned char v11 = W[4 * (j - 1) + 2]; unsigned char v12 = W[4 * (j - 1) + 3]; if ( (j & 3) == 0 ){
v10 = S[v11]; v11 = S[v12]; v12 = S[W[4 * (j - 1)]]; v9 = S[W[4 * (j - 1) + 1]] ^ Rcon[j >> 2];}
W[(4 * j)] = v9 ^ W[4 * (j - 4)]; W[(4 * j) + 1] = v10 ^ W[4 * (j - 4) + 1]; W[(4 * j) + 2] = v11 ^ W[4 * (j - 4) + 2]; W[(4 * j) + 3] = v12 ^ W[4 * (j - 4) + 3];}
}