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}