April 4, 2022

DASCTF2022 X SU-login

(开篇先感谢Helen师傅对我的醍醐灌顶)

题目介绍,这题的原型Helen师傅某次hvv挖洞时碰到的,于是经过精心修改策划出的一道十分值得学习的题。(个人非常喜欢这种题)

题目知识点:

  1. Socket技术

  2. RSA数字签名

  3. Hill密码

  4. 魔改AES

好,现在就开始这趟login旅程!

0x00 日常查壳

一开始会收到两个文件login与check,都是无壳的ELF64文件

image-20220404190619800

0x01 Socket进程通信

不熟悉的师傅们可能会直接懵了,虽然会找到少许逻辑,但是感觉完全没个流程,动调也动调不起来(先埋个雷)

但我们应该先该思考下这两个文件是各自干什么的,随便找到文件进入到一个奇怪的函数

image-20220404191147942

可以确定这肯定不是自写的函数(毕竟前面甚至还有个sys),于是我们直接用搜索引擎,不难搜到这是个Socket的常用函数,这时候再点开其他几个奇怪函数,可以发现确定是Socket,让这两个文件进行通信

参考文章:

单单审计这篇够了

尝试去实现Socket通信可以看下这篇

如果花少许时间读完一篇是可以知道各个系统函数的大意,这时候通过函数的引用是可以确定有个sockaddr_in 结构体的存在

通过Socket函数的各种引用,是可以确定我们的具体变量结构体是哪个

我知道结构体了,需要让IDA也标识出,于是shift + F9或在这添加我们的sockaddr_in结构体

image-20220404192027578

再到所在页面空白处右击Add struct type -> Add standard structure -> Ctrl + F 搜索sockaddr_in

添加后,再到所在变量右击修改变量类型选择sockaddr_in即可修改变量(回到反编译界面记得F5刷新)

image-20220404192639461

这时候我们再去审计整个代码,通过遇到不懂的函数直接搜,即可审计出两个文件是如何通信

贴下我的分析

login(客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char *v3; // rsi
__int64 v4; // rdx
__int64 v5; // rcx
__int64 v6; // r8
u32 v7; // er9
__int64 v8; // rax
__int64 len; // rax
unsigned int v10; // [rsp+10h] [rbp-800h]
int v11; // [rsp+14h] [rbp-7FCh]
__int64 ipAddr; // [rsp+18h] [rbp-7F8h]
sockaddr_in v13; // [rsp+20h] [rbp-7F0h] BYREF
__int64 msg[2]; // [rsp+30h] [rbp-7E0h] BYREF
char v15[984]; // [rsp+40h] [rbp-7D0h] BYREF
u32 input[2]; // [rsp+420h] [rbp-3F0h] BYREF
__int64 v17; // [rsp+428h] [rbp-3E8h]
char v18[984]; // [rsp+430h] [rbp-3E0h] BYREF
unsigned __int64 v19; // [rsp+808h] [rbp-8h]

v19 = __readfsqword(0x28u);
msg[0] = 0LL;
msg[1] = 0LL;
memset(v15, 0, sizeof(v15));
*(_QWORD *)input = 0LL;
v17 = 0LL;
memset(v18, 0, sizeof(v18));
if ( argc != 2 )
{
printf((__int64)"Usage: %s <IP Address>\n", *argv);
exit(1);
}
ipAddr = sub_454A70((__int64)argv[1]);
if ( !ipAddr )
exit(1);
v10 = sub_4546D0(2LL, 1LL, 0LL);
if ( v10 == -1 )
exit(1);
*(_QWORD *)&v13.sin_family = 2LL;
*(_QWORD *)v13.sin_zero = 0LL;
v13.sin_port = ROL(port);
*(_QWORD *)&v13.sin_addr.s_addr = ***(unsigned int ***)(ipAddr + 24);
if ( (unsigned int)sys_connect(v10, (struct sockaddr *)&v13, 16) == -1 )
exit(1);
while ( 1 )
{
v11 = sys_recvfrom(v10, msg, 0x3E8uLL, 0);
if ( v11 < 0 )
break;
*((_BYTE *)msg + v11) = 0;
v3 = (char *)msg + 1;
printf((__int64)"%s", (const char *)msg + 1);
if ( LOBYTE(msg[0]) == '0' ) // 结束通信
{
sys_close(v10);
exit(0);
}
if ( LOBYTE(msg[0]) == '2' ) // 发送AES数据
{
v8 = length();
v3 = SBox;
sys_sendto(v10, SBox, v8, 0);
}
sub_418A40(input, (__int64)v3, v4, v5, v6, v7);// 接受数据之类的函数
len = length();
sys_sendto(v10, input, len, 0);
}
exit(1);
}

check(服务端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int64 v3; // rdx
__int64 v4; // rcx
int v5; // er8
int v6; // er9
int result; // eax
char v8; // [rsp+0h] [rbp-50h]
int v9; // [rsp+Ch] [rbp-44h] BYREF
int v10; // [rsp+10h] [rbp-40h] BYREF
unsigned int sock; // [rsp+14h] [rbp-3Ch]
unsigned int v12; // [rsp+18h] [rbp-38h]
int v13; // [rsp+1Ch] [rbp-34h]
sockaddr_in addr; // [rsp+20h] [rbp-30h] BYREF
struct sockaddr v15; // [rsp+30h] [rbp-20h] BYREF
unsigned __int64 v16; // [rsp+48h] [rbp-8h]

v16 = __readfsqword(0x28u);
sock = sys_socket(2, 1, 0);
if ( sock == -1 )
exit(1);
v10 = 2;
sys_setsockopt(sock, 1, 2, (char *)&v10, 4);
*(_QWORD *)&addr.sin_family = 2LL;
*(_QWORD *)addr.sin_zero = 0LL;
addr.sin_port = sub_55CCC0(dword_609150);
addr.sin_addr.s_addr = sub_55CCB0(0);
if ( (unsigned int)sys_bind(sock, (struct sokaddr *)&addr, 16) == -1 )
exit(1);
if ( (unsigned int)sys_listen(sock, 100) == -1 )
exit(1);
printf((__int64)"\n----------------Welcome----------------");
v9 = 16;
while ( 1 )
{
v12 = sys_accept(sock, &v15, &v9);
if ( !v12 )
break;
if ( v12 == -1 )
exit(1);
v13 = sys_clone();
if ( v13 <= 0 )
{
if ( !v13 )
{
sys_ptrace(0LL, (__int64)&v15, v3, v4, v5, v6, v8);
sys_close(sock);
LinkStart(v12, *(__int64 *)&v15.sa_family, *(unsigned __int64 *)&v15.sa_data[6]);
exit(0);
}
exit(0);
}
sys_close(v12);
}
result = 0;
if ( __readfsqword(0x28u) != v16 )
sub_55CC60();
return result;
}

反调试

拆一下上面的那颗雷,这是一个端口反调试,在客户端填入端口的那个函数可以发现数据区1234 + 22712正好是23946,而这个端口正式linux_server的默认端口

而在服务端check,bind默认启用的是1234端口,所以我们linux_server的默认端口23946直接调试就会挂

解决方法:我们直接linux_server监听1234端口

1
./linux_server64 -p 1234

设置好监听端口

image-20220404200425127

直接开跑,但其实会因为子进程的关系,fork函数(IDA不太会设置了,如果有师傅会请务必ddw!),跑到这我们可以选择静态分析了

image-20220404200219840

处理完两个文件的通信,个人已经学到了很多,接下来再来认识一下大家最爱用的RSA!

通过字符串查找与主程序的执行流程,不难找到了我们三次输入的地方(每次都是服务端check发送,客户端login输出,客服端接受输入,服务端接受处理数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
unsigned __int64 __fastcall LinkStart(unsigned int sock, __int64 a2, unsigned __int64 a3)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v6; // rax
__int64 v7; // rax
unsigned __int64 result; // rax
int v10; // [rsp+28h] [rbp-898h]
int v11; // [rsp+28h] [rbp-898h]
int v12; // [rsp+28h] [rbp-898h]
int v13; // [rsp+2Ch] [rbp-894h]
char v14[32]; // [rsp+30h] [rbp-890h] BYREF
char v15[32]; // [rsp+50h] [rbp-870h] BYREF
char v16[48]; // [rsp+70h] [rbp-850h] BYREF
char v17[64]; // [rsp+A0h] [rbp-820h] BYREF
__int64 matrixData[2]; // [rsp+E0h] [rbp-7E0h] BYREF
char v19[984]; // [rsp+F0h] [rbp-7D0h] BYREF
__int64 rsaData[2]; // [rsp+4D0h] [rbp-3F0h] BYREF
char v21[984]; // [rsp+4E0h] [rbp-3E0h] BYREF
unsigned __int64 v22; // [rsp+8B8h] [rbp-8h]

v22 = __readfsqword(0x28u);
matrixData[0] = 0LL;
matrixData[1] = 0LL;
memset(v19, 0, sizeof(v19));
rsaData[0] = 0LL;
rsaData[1] = 0LL;
memset(v21, 0, sizeof(v21));
strcpy(v14, "Please enter the token: ");
strcpy(v15, "Please input the password: ");
strcpy(v17, "Login successful.\nNow, you can enter flag to verify: ");
strcpy(v16, "Forbidden, wrong token or password.\n");
sys_sendto(sock, "1", 1uLL, 0);
v3 = length((__int64)v14);
sys_sendto(sock, v14, v3, 0);
v10 = sys_recvfrom(sock, rsaData, 1000uLL, 0);
if ( v10 >= 0 )
{
*((_BYTE *)rsaData + v10) = 0;
sys_sendto(sock, "1", 1uLL, 0);
v4 = length((__int64)v15);
sys_sendto(sock, v15, v4, 0);
v11 = sys_recvfrom(sock, matrixData, 0x3E8uLL, 0);
if ( v11 >= 0 )
{
*((_BYTE *)matrixData + v11) = 0;
if ( RSA((__int64)rsaData) && (unsigned int)Hill((__int64)matrixData, (__int64)matrixData) )// RSA && Matrix
{
sys_sendto(sock, "2", 1uLL, 0);
v6 = length((__int64)v17);
sys_sendto(sock, v17, v6, 0);
v12 = sys_recvfrom(sock, SBox, 0x3E8uLL, 0);
if ( v12 >= 0 )
{
SBox[v12] = 0;
CovertArray((__int64)SBox); // 转换为数组后 可以取查下数组 会发现是AES
v13 = sys_clone();
if ( v13 <= 0 )
{
if ( !v13 )
{
AES(sock, a2, a3, (int *)rsaData, (int *)matrixData);
exit(0);
}
exit(0);
}
sys_close(sock);
}
}
else
{
sys_sendto(sock, "0", 1uLL, 0);
v7 = length((__int64)v16);
sys_sendto(sock, v16, v7, 0);
}
}
}
sys_close(sock);
result = __readfsqword(0x28u) ^ v22;
if ( result )
sub_55CC60();
return result;
}

0x02 RSA

关于RSA的文章实在是太多太多,各位师傅可以自己去找篇喜欢的学习一番

那如果我们在比赛中并没有看出来这是RSA?一般的逻辑都是我们先试试能看懂什么函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
_BOOL8 __fastcall RSA(__int64 a1)
{
unsigned __int8 *v1; // rax
__int64 v2; // r8
_BOOL8 result; // rax
int v4[4]; // [rsp+10h] [rbp-310h] BYREF
int v5[4]; // [rsp+20h] [rbp-300h] BYREF
int v6[4]; // [rsp+30h] [rbp-2F0h] BYREF
int v7[6]; // [rsp+40h] [rbp-2E0h] BYREF
char v8[70]; // [rsp+5Ah] [rbp-2C6h] BYREF
char v9[632]; // [rsp+A0h] [rbp-280h] BYREF
unsigned __int64 v10; // [rsp+318h] [rbp-8h]

v10 = __readfsqword(0x28u);
strcpy( // 大数 并且可以猜测是质数
v9,
"13123058934861171416713230498081453101147538789122070079961388806126697916963123413431108069961369055630747412550900"
"23940271082784791796087035865396294828238135174112188452839936976453044650993624026229024830522655211710058472661625"
"52929639711415105186785526790332203152463777462705158539879031845129488013974521045545898037256190760663399689993089"
"10127885089547678968793196148780382182445270838659078189316664538631875879022325427220682805580410213245364855569367"
"70291915788136708567728312473287462156937990127266216202578060866957754654833327476605875578644949127700234991859897"
"1841605936268030140638579388226573929");
strcpy(&v8[6], "By reading we enrich the mind, by conversation we polish it.");
strcpy(v8, "10001"); // 很像e指数 可以去查0x10001
sub_406990(v4, (unsigned __int8 *)v9, 10);
v1 = (unsigned __int8 *)b2a_Hex((__int64)&v8[6]);// 猜测是密文
sub_406990(v5, v1, 16);
sub_406990(v6, (unsigned __int8 *)v8, 16);
sub_406990(v7, (unsigned __int8 *)a1, 10);
sub_4069B0(v7, (__int64)v7, (__int64)v6, (__int64)v4, v2);
result = (unsigned int)sub_406910((__int64)v7, (__int64)v5) == 0;
if ( __readfsqword(0x28u) != v10 )
sub_55CC60();
return result;
}

除了b2a_Hex这个函数,其他函数又臭又长,作者当然不会让各位师傅去逆这些,肯定是一些加密,我们就是通过已有的数据去搜寻这到底是什么加密

经过简单的搜索或多看几篇文章是可以发现这是RSA加密

image-20220404201419159

再了解完RSA加密后,我们简单写下脚本即出我们的token!

通过yafu或者网站或者什么工具直接分解n

(网站:http://www.factordb.com/index.php)

要注意的是密文是小端序,不过我们可以通过两次尝试去看看到底是什么顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

0x03 Hill

其实就是Hill密码,这次没看出来没关系,下次就一定认的出来(我也没认出来

通过线代矩阵的求出的一组数据与rand()比较,那么矩阵有了,加密后的数据有了,最好的办法是通过sage求一个有限域内的逆矩阵后和密文矩阵相乘求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
__int64 __fastcall Hill(__int64 input, __int64 a2)
{
__int64 result; // rax
int i; // [rsp+10h] [rbp-80h]
int j; // [rsp+14h] [rbp-7Ch]
unsigned int v5; // [rsp+18h] [rbp-78h]
int k; // [rsp+1Ch] [rbp-74h]
__int64 encInput[4]; // [rsp+20h] [rbp-70h]
int v8; // [rsp+40h] [rbp-50h]
__int64 v9; // [rsp+50h] [rbp-40h]
__int64 v10; // [rsp+58h] [rbp-38h]
__int64 v11; // [rsp+60h] [rbp-30h]
__int64 v12; // [rsp+68h] [rbp-28h]
int v13; // [rsp+70h] [rbp-20h]
unsigned __int64 v14; // [rsp+78h] [rbp-18h]

v14 = __readfsqword(0x28u);
encInput[0] = 0xA34C0F7A2E25DB71LL; // 因为内存是连续的 所以这段密文通过unsigned _int8后是36的长度
encInput[1] = 0x7AA91B6E29AA226ALL;
encInput[2] = 0x56AA0EF0802F278ALL;
encInput[3] = 0x9AF6F2A9005859F7LL;
v8 = 0xC9481C4E;
v9 = 0xFCF1BE5355A297A3LL;
v10 = 0xE2E91314526B79F9LL;
v11 = 0x275708561F8E512DLL;
v12 = 0x75778252D0D405A7LL;
v13 = 0xED4A991B;
ConvertArray(input);
for ( i = 0; i <= 5; ++i )
{
for ( j = 0; j <= 5; ++j )
{
v5 = 0;
for ( k = 0; k <= 5; ++k )
v5 += *((unsigned __int8 *)encInput + 6 * i + k) * *(unsigned __int8 *)(6 * k + j + input);// 线性计算 + 矩阵乘法
if ( v5 % 0x101 != (int)rand() % 255 ) // 已知密文
{
result = 0LL;
goto LABEL_13;
}
}
}
result = 1LL;
LABEL_13:
if ( __readfsqword(0x28u) != v14 )
sub_55CC60();
return result;
}

未初始化种子,所以数据可以直接拿)

这里在我复现的时候一直出问题原因在于!linux和windows所出的rand是不同的!

原文

image-20220404210836927

那么rand()直接自己写个C程序拿出数据,直接可以拿sagemath就可跑出答案

1
2
3
4
5
6
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)

image-20220404202514040

1
5132d202c32d95b9f978d514e3294220513b15623482b4c02e9afde8bad5ec07486a5488

验证下数据,发现我们成功login,开始输入flag!

image-20220404202841053

0x04 AES

成功进入之后,我们可以看到发送了数据过去,当我们不知道是SBox的情况下,我们可以先往下看

image-20220404203042241

ConvertArray这个函数,稍微看一下就可以理解到位,字符串转成了两个两个一组的数组

这时候我们,可以分开去搜这个数组,会很直观的知道这是个AES加密

image-20220404203319498

进到AES函数里,首先会是提取我们之前输入的token盒passwd,注意要用linux下写,直接提取出即可

同样的要注意rand的特性,可以根据后面代码可以知道前16位是key后36位是iv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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了!

image-20220405122953242

0x05 GetFlag!

这个AES没有那么简单,如果师傅们想理解到位建议读一下AES的详细文章

个人强烈推荐这篇

泻药,刚打完了Helen师傅的魔改AES!下午打完羽毛球就回来开录,我讲解是肯定没有各位去认真读遍文章学的更好

简单介绍一下到底魔改了什么

  1. S(加密盒)和Re(解密盒)互换,也就是加密的时候用了解密盒,解密的时候用了加密盒

  2. 密钥扩展改变了Rcon轮常量值

  3. 再进入加密器前进行了一轮iv的异或(像是ECB模式前加了轮异或)

PS:最大的改动是全部都是8位运算!!(而AES加密是32位运算,所以需要自己重写一下)

(个人感觉是如果不自己打遍AES,审起来会比较累,同时AES是经常用的学习一番也是十分不错的)

img

贴一下我写的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#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];
}
}

成功得到最后的flag

image-20220405123036582

GetFlag!

image-20220405123105480

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.