NSSCTF Round#18 WP
文章目录
users & users_revenge
两道题可以用同一个脚本解。两道题区别就是题目 1 给出 200 个用户名,题目 2 给出 500 个用户名,密码为用户名的 MD5,其中有一个用户里有 flag,考虑使用 pwntools 连接。
1from pwn import *
2import hashlib
3
4usrlst = []
5pwdlst = []
6flaglst = []
7sususrlst = []
8suspwdlst = []
9excptlst = []
10'''
11file_name =
12given_host =
13given_port =
14'''
15with open(file_name, 'r') as wordlist:
16 for line in wordlist:
17 username = line.rstrip() # 去除换行符
18 usrlst.append(username)
19 pwdlst.append(hashlib.md5(username.encode()).hexdigest())
20
21for i in range(len(usrlst)):
22 try:
23 print(i)
24 shell = ssh(host = given_host, port = given_port, user = usrlst[i], password = pwdlst[i]) # 题目使用 SSH 连接
25 sh = shell.run('ls -a') # 不知道文件名与路径,先使用 ls -a 探探虚实。最后知道 flag.txt 确实是一个隐藏文件
26 flag = sh.recvall()
27 if len(flag) != 47: # 如果没有 flag,接收到默认文件名和路径名一共是 47 Bits,有 flag.txt 则会接收到 56 Bits
28 flaglst.append(flag)
29 sususrlst.append(usrlst[i])
30 suspwdlst.append(pwdlst[i])
31 shell.close()
32 except:
33 continue
34 excptlst.append(i)
35
36print(flaglst)
37print(sususrlst)
38print(suspwdlst)
39print(excptlst)
由于不知道存储 flag 文件的文件名以及路径,这一脚本只用于筛选可疑用户,找到可疑用户后再手动搜索 flag。不过好在每一道题中都只筛选出了一个可疑用户
炒鸡常见的编码哇
IDA 打开程序,通过字符串找到程序的核心部分:
1int sub_40160E()
2{
3 int v0; // eax
4 int v1; // edx
5 int v2; // eax
6 char v4[9]; // [esp+17h] [ebp-21h] BYREF
7 unsigned int k; // [esp+20h] [ebp-18h]
8 int j; // [esp+24h] [ebp-14h]
9 int i; // [esp+28h] [ebp-10h]
10 int v8; // [esp+2Ch] [ebp-Ch]
11
12 sub_40C270();
13 v8 = 0;
14 sub_4A3450(&dword_4B0960, "Please input Your flag to start Happy New Year!!");
15 sub_46AFB0(sub_4A1540);
16 sub_4A4130(&dword_4B0780, Str);
17 dword_4F8924 = 8 * strlen(Str);
18 for ( i = 0; Str[i]; ++i )
19 {
20 sub_4014BC(Str[i], v4);
21 for ( j = 0; j <= 7; ++j )
22 {
23 v0 = v8++;
24 byte_4F8120[v0] = v4[j];
25 }
26 }
27 for ( k = 0; k < dword_4F8924; k += 6 )
28 {
29 sub_40150A(&byte_4F8120[k]);
30 v1 = sub_4015CA(&byte_4F8120[k]);
31 v2 = dword_4F8A40++;
32 byte_4F8940[v2] = byte_4AF020[v1];
33 }
34 if ( !memcmp(byte_4F8940, &unk_4AF080, 0x80u) )
35 sub_4A3450(&dword_4B0960, "Good Job!");
36 else
37 sub_4A3450(&dword_4B0960, "Try Again!");
38 sub_46AFB0(sub_4A1540);
39 return 0;
40}
下面是另外三个重要函数 sub_4014BC()
、sub_40150A()
与 sub_4015CA()
的定义:
1int __cdecl sub_4014BC(char a1, int a2)
2{
3 int result; // eax
4 int i; // [esp+10h] [ebp-4h]
5
6 for ( i = 7; i >= 0; --i )
7 *(7 - i + a2) = (a1 >> i) & 1;
8 result = a2 + 8;
9 *(a2 + 8) = 0;
10 return result;
11}
12
13int __cdecl sub_40150A(int a1)
14{
15 int result; // eax
16 int i; // [esp+18h] [ebp-10h]
17 int v3; // [esp+18h] [ebp-10h]
18 int v4; // [esp+1Ch] [ebp-Ch]
19
20 v4 = 0;
21 for ( i = 0; i <= 255; i = v3 + 1 )
22 {
23 v3 = (i + 1) % 256;
24 v4 = (v3 + v4 + 1) % 256;
25 result = sub_4A1658(v3 % 6 + a1, v4 % 6 + a1);
26 }
27 return result;
28}
29
30int __cdecl sub_4015CA(int a1)
31{
32 int i; // [esp+4h] [ebp-Ch]
33 int v3; // [esp+8h] [ebp-8h]
34 int v4; // [esp+Ch] [ebp-4h]
35
36 v4 = 0;
37 v3 = 1;
38 for ( i = 5; i >= 0; --i )
39 {
40 v4 += v3 * *(i + a1);
41 v3 *= 2;
42 }
43 return v4;
44}
sub_4014BC()
将输入字符串的每一个字节转化为八位二进制数并存储进新的数组,在 sub_40160E()
中,输入字符串的二进制形式被存储在 byte_4F8120
中,这一转换过程在第一个 for
循环语句中完成。
sub_4015CA()
将二进制数组六个一组进行编码,在 sub_40160E()
的第二个 for
循环语句中,程序完成了变种 Base64 重新编码的过程。编码表存储在 byte_4AF020
中。
sub_40150A()
比较难以理解,但是大体能看出是在以六字节个为一组进行改变顺序的操作。经过调试可以知道该函数改变顺序的逻辑:
调整前索引 | 调整后索引 |
---|---|
0 | 0 |
1 | 2 |
2 | 4 |
3 | 5 |
4 | 3 |
5 | 1 |
具体调试的过程,就是在 byte_4F8120
被调整顺序前,将其修改为由 1 个 1 和 5 个 0 组成的六位二进制数,通过改变 1 的位置查看 sub_40150A()
打乱顺序的方式。
编码后的字符串存储在 byte_4F8940
中。解题脚本如下:
1from Crypto.Util.number import *
2
3cipher = [0xE2, 0xF7, 0xD3, 0xE2, 0xC8, 0xB4, 0xD8, 0xC5, 0xCF, 0xB4,
4 0xE7, 0xEE, 0xE1, 0xD9, 0xF1, 0xEF, 0xCB, 0xEB, 0xD9, 0xC9,
5 0xCE, 0xC5, 0xD9, 0xE5, 0xCC, 0xB7, 0xD1, 0xED, 0xE0, 0xB4,
6 0xF1, 0xEE, 0xE0, 0xE7, 0xD2, 0xF6, 0xCB, 0xF3, 0xC9, 0xF3,
7 0xD3, 0xD5, 0xEF]
8table = [0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
9 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3,
10 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xE0, 0xE1, 0xE2, 0xE3,
11 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
12 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
13 0xF8, 0xF9, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6,
14 0xB7, 0xB8, 0xAA, 0xAE]
15
16binaries = ''
17for i in cipher:
18 binaries += str(bin(table.index(i)))[2:].rjust(6, '0') # 将每个编码转成 6 位二进制数
19blst = list(binaries) # 拼接成一个 258 位二进制数
20
21flag = ['N'] * 258
22for i in range(0, 258, 6): # 恢复正确顺序
23 flag[i] = blst[i]
24 flag[i + 1] = blst[i + 2]
25 flag[i + 2] = blst[i + 4]
26 flag[i + 3] = blst[i + 5]
27 flag[i + 4] = blst[i + 3]
28 flag[i + 5] = blst[i + 1]
29
30print(long_to_bytes(int(''.join(flag)[:-2], 2)).decode()) # 舍弃最后补位的两个 0
31
32# output: NSSCTF{Y0u_4reThe_K1ng_0fBase64}