近期解题 2024.2.16

文章目录

春秋杯 - upx2023

程序放进 Exeinfo,其实听名字就知道有壳,但是这个壳改过。

010 Editor 里看一眼,这个壳改得挺没品的,upx 段标识改回大写后,顺利脱壳。

进入 IDA 分析:

 1int __fastcall main(int argc, const char **argv, const char **envp)
 2{
 3  std::ostream *v3; // rax
 4  char *v4; // rax
 5  int v6[44]; // [rsp+20h] [rbp-60h] BYREF
 6  char v7[16]; // [rsp+D0h] [rbp+50h] BYREF
 7  char v8[16]; // [rsp+E0h] [rbp+60h] BYREF
 8  char v9[20]; // [rsp+F0h] [rbp+70h] BYREF
 9  int v10; // [rsp+104h] [rbp+84h]
10  unsigned int Seed; // [rsp+108h] [rbp+88h]
11  int i; // [rsp+10Ch] [rbp+8Ch]
12
13  _main();
14  Seed = time(0i64);
15  srand(Seed);
16  std::string::string(v7);
17  std::operator<<<std::char_traits<char>>(&std::cout, Str);
18  std::operator>><char>(&std::cin, v7);
19  std::string::string(v9, v7);
20  change(v8, v9);
21  std::string::operator=(v7, v8);
22  std::string::~string(v8);
23  std::string::~string(v9);
24  if ( std::string::length(v7) != 42 )
25  {
26    v3 = std::operator<<<std::char_traits<char>>(&std::cout, "len error");
27    std::endl<char,std::char_traits<char>>(v3);
28    exit(0);
29  }
30  qmemcpy(v6, &unk_46A020, 0xA8ui64);
31  for ( i = 0; i <= 41; ++i )
32  {
33    v10 = rand() % 255;
34    v4 = std::string::operator[](v7, i);
35    if ( (v10 ^ *v4) != v6[i] )
36      exit(0);
37  }
38  std::string::~string(v7);
39  return 0;
40}

其中 change() 函数如下:

 1std::string *__fastcall change(std::string *a1, std::string *a2)
 2{
 3  __int64 v2; // rdi
 4  void *v3; // rsp
 5  _BYTE *v4; // rax
 6  _BYTE v6[32]; // [rsp+0h] [rbp-80h] BYREF
 7  __int64 v7[7]; // [rsp+20h] [rbp-60h] BYREF
 8  char v8; // [rsp+5Fh] [rbp-21h] BYREF
 9  __int64 *v9; // [rsp+60h] [rbp-20h]
10  __int64 v10; // [rsp+68h] [rbp-18h]
11  __int64 v11; // [rsp+70h] [rbp-10h]
12  int v12; // [rsp+7Ch] [rbp-4h]
13  int v13; // [rsp+80h] [rbp+0h]
14  int n; // [rsp+84h] [rbp+4h]
15  int m; // [rsp+88h] [rbp+8h]
16  int k; // [rsp+8Ch] [rbp+Ch]
17  char v17; // [rsp+93h] [rbp+13h]
18  int v18; // [rsp+94h] [rbp+14h]
19  int j; // [rsp+98h] [rbp+18h]
20  int i; // [rsp+9Ch] [rbp+1Ch]
21
22  v7[5] = v6;
23  v13 = 3;
24  std::allocator<char>::allocator(&v6[95]);
25  std::string::string(a1, &unk_47F000, &v8);
26  std::allocator<char>::~allocator(&v8);
27  v12 = std::string::length(a2);
28  v11 = v12 - 1i64;
29  v7[1] = 0i64;
30  v2 = v12;
31  v10 = v13 - 1i64;
32  v7[0] = v11;
33  v7[2] = v12;
34  v7[3] = 0i64;
35  v3 = alloca(16 * ((v12 * v13 + 15) >> 4));
36  v9 = v7;
37  for ( i = 0; i < v13; ++i )
38  {
39    for ( j = 0; j < v12; ++j )
40      *(v9 + j + v2 * i) = 10;
41  }
42  v18 = 0;
43  v17 = 0;
44  for ( k = 0; k < v12; ++k )
45  {
46    if ( !v18 || v13 - 1 == v18 )
47      v17 ^= 1u;
48    v4 = std::string::operator[](a2, k);
49    *(v9 + k + v2 * v18) = *v4;
50    if ( v17 )
51      ++v18;
52    else
53      --v18;
54  }
55  for ( m = 0; m < v13; ++m )
56  {
57    for ( n = 0; n < v12; ++n )
58    {
59      if ( *(v9 + n + v2 * m) != 10 )
60        std::string::operator+=(a1);
61    }
62  }
63  return a1;
64}

打眼一看,这个函数除了该顺序之外没干别的事,遂打断点动调找调整过的顺序顺序。输入 "flag{1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ}",得到如下顺序:

1>>> ['flag{1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ}'.index(i) for i in 'f{48BFJNRVZlg13579ACEGIKMOQSUWY}a260DHLPTX']
2[0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38]

随后处理 rand() % 255,我试图爆破,但是误将调整后的 rand() 顺序当作调整前的 rand() 顺序而没能成功得到 Seed。赛后我修改了顺序得到 Seed,可以说是最遗憾的一集了。

程序编译好的时间对应的时间戳是 1685762995,因此 Seed 一定在这之前出现。flag 的前几个字符 “flag{” 已知,可以计算出随机数序列中第 0、11、32、12、1 的值分别为 0x6F、0xAA、0x9B、0x2、0x18。用下面的脚本爆破:

 1#include <iostream>
 2#include <cstdlib>
 3#include <ctime>
 4
 5using namespace std;
 6
 7int main()
 8{
 9    int num[32] = {0};
10    for(unsigned int seed = 1662973302; seed < 1685762995; seed++)
11    {
12        for(int i = 0; i <= 32; i++)
13        {
14            num[i] = rand() % 255;
15        }
16        if(num[0] == 0x6F && num[1] == 0x18 && num[11] == 0xAA && num[12] == 0x2 && num[32] == 0x9B)
17        {
18            cout << seed << endl;
19        }
20    }
21}
22
23// output: 1682145110

得到了种子就可以写解密脚本了:

 1#include <iostream>
 2#include <cstdio>
 3#include <cstdlib>
 4
 5using namespace std;
 6
 7int main()
 8{
 9    int seq[42] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38};
10    int cip[42] =
11    { 
12        0x09, 0x63, 0xD9, 
13        0xF6, 0x58, 0xDD, 0x3F, 0x4C, 
14        0x0F, 0x0B, 0x98, 0xC6, 0x65, 
15        0x21, 0x41, 0xED, 0xC4, 0x0B, 
16        0x3A, 0x7B, 0xE5, 0x75, 0x5D, 
17        0xA9, 0x31, 0x41, 0xD7, 0x52, 
18        0x6C, 0x0A, 0xFA, 0xFD, 0xFA, 
19        0x84, 0xDB, 0x89, 0xCD, 0x7E, 
20        0x27, 0x85, 0x13, 0x08
21    };
22    int seed = 1682145110;
23    srand(seed);
24    int flag[42] = {0};
25
26    for(int i = 0;i < 42; i++)
27    {
28        flag[i] = (rand() % 255) ^ cip[i];
29    }
30    for(int j = 0;j < 42; j++)
31    {
32        cout << flag[seq[j]];
33    }
34}

flag{0305f8f2-14b6-fg7b-bc7a-010299c881e1}

攻防世界 - handcrafted-pyc

我在第七周的解题记录里完成了这道题目的一部分,但是并没有完全解出来,今天填坑。

题目附件是 .py 的源代码:

1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import marshal, zlib, base64
5
6exec(marshal.loads(zlib.decompress(base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ=='))))

代码阅读上并没有什么难度,首先把一个字符串 Base64 解码,然后 zlib 解压缩,再然后得到反序列化,最后反序列化成 Python 代码并执行。

看上去肯简单对吧,但是运行一下,果然报错:

1ValueError: bad marshal data (unknown type code)

看来是反序列化那一步出了问题。我们不妨先拿到解压后的 .pyc 字节码再进行下一步分析。如下图,我们得到的 .pyc 文件缺少 magic number。

我尝试了几个不同版本 Python 的 magic number,但都会提示反编译失败。不过,尽管没有得到源代码,在使用 uncompyle6 时仍然得到了一段 Bytecode。第七周那一次周报里,我把完整的 Bytecode 贴上了。下面贴一小段简单分析一下:

 1 L.   1         0  LOAD_GLOBAL           0  'chr'
 2                3  LOAD_CONST               108
 3                6  CALL_FUNCTION_1       1  None
 4                9  LOAD_GLOBAL           0  'chr'
 5               12  LOAD_CONST               108
 6               15  CALL_FUNCTION_1       1  None
 7               18  LOAD_GLOBAL           0  'chr'
 8               21  LOAD_CONST               97
 9               24  CALL_FUNCTION_1       1  None
10               27  LOAD_GLOBAL           0  'chr'
11               30  LOAD_CONST               67
12               33  CALL_FUNCTION_1       1  None
13               36  ROT_TWO          
14               37  BINARY_ADD       
15               38  ROT_TWO          
16               39  BINARY_ADD       
17               40  ROT_TWO          
18               41  BINARY_ADD

程序依次将 chr(108)chr(108)chr(97)chr(67) 入栈,随后 ROT_TWO 将栈顶的两个字符交换顺序,BINARY_ADD 将栈顶的两个元素相加(字符与字符相加得到一个字符串)。这一段字节码执行结束后可以在栈顶得到 'Call' 这个单词。

后面的字节码和这一段类似,都是将数个字符入栈并且倒序输出。可以用以下脚本把全部字符提取并且输出(可以说是一个小小虚拟机):

 1import re
 2
 3string = ''
 4
 5find = True
 6buffer = ''
 7
 8with open(r'C:\Users\jack_gdn\Desktop\temp files\攻防世界 - handcrafted-pyc\download.txt', 'r') as file:
 9    for line in file:
10        load_const = re.search(r'LOAD_CONST\s{15}(\d+)', line)
11        rot_two = re.search(r'ROT_TWO', line)
12        if load_const:
13            buffer = buffer + chr(int(load_const.group(1)))
14            find = True
15        if rot_two and find:
16            print(buffer[::-1], end = '')
17            find = False
18            buffer = ''
19
20# output: Call me a Python virtual machine! I can interpret Python bytecodes!!!hitcon{Now you can compile and run Python bytecode in your brain!}password: Wrong password... Please try again. Do not brute force. =)

hitcon{Now you can compile and run Python bytecode in your brain!}

NSSCTF - can_can_need_pxory

近期最让我绷不住的一道题

题目附件有一个文本文件和一个程序。文本文件如下:

经过唯一性处理后,print结果如下:

NDc1NTMyNTQ0NzRGNDI1OTQ2NTEzMjU0NDc0RDVBNTU0NzQxNTc0NDQzNEQ0QTUzNDczNDVBNTQ0NTRDNDI1MzQ3NEQ1OTU0NEQ0QzQyNTI0NzQ1MzM1NDUxNEU0QTUzNDY1MTMyNDQ1MzRENTI1NTQ3NTE1NzQ0NEI0RDRBNTM0ODQ1NUE0MzU5NEQ1MjU0NDczNDM0NDM1OTRFNDI1NzQ3NDUzMzU0NDU0QzQyNTI0NzQxMzM1NDRENEQ0QTUzNDY1MTU5NTQ0MzRGNDI1OTQ3MzQzMzQzNTk0RDUyNTQ0NzQ5MzM0MzU5NEQ0QTUyNDczNDM0NDQ0QjRENTI0RDQ3NTEzMzQ0NDM0RTVBNTM0NjUxNTk1NDQ1NEQ0MjVBNDc0OTMyNDM1OTRENTI1NDQ3NEQ1OTQzNTk0RDRBNTI0NzU5MzQ0NDQ1NEY0MjRENDc0NTVBNDQ0NzRGNEE1QTQ3NTk1NzQ0NDk0RTUyNTI0NzM0NUE0MzU5NEQ1MjU0NDc0OTM0NTM1OTRENEE1MjQ4NDUzNDU0NDE0RDQyNEQ0NzQ1NTk1NDQ1NEU1QTU0NDc0OTU3NDQ0MzRENEE1MzQ3MzQ1QTU0NDU0QzQyNTM0NzREMzQ0NDRENEM0MjUyNDc0OTVBNTQ1MzRGNEE1NzQ2NTEzMjU0NDE0RDUyNTc0ODQxNTc0NDRCNEY0MjU1NDc1OTU5NDM1OTRENTI1NDQ3NDUzNDUzNTk0RDRBNTM0NzREMzQ1NDUzNEU1MjRENDc0NTU5NDQ0MzRFNDI1NzQ4NDE1NzQ0NDM0RDUyNTQ0ODQ1MzQ1NDRENEM0MjUzNDc0RDU5NTQ1MzRDNDI1NTQ4NDU1QTQ0NDk0RTQyNEQ0NzU1MzM1NDQ5NEQ1QTU3NDY1MTMyNTQ0RDRFNDI1MjQ3NDk1NzQ0NDU0RDVBNTQ0NzQ1NTc0MTNEM0QzRDNE

请将解出的flag用NSSCTF{}包裹一下喵

运行程序,会输出这么一坨东西:

这个程序使用 Pyinstaller 打包,逆向得到源代码:

 1print('ccccccccccccccccccccccccccccccccccc!')
 2print('D0 U know C?')
 3print('\n#include <NSSCTF.h>\nv01d bnssst(int FTC[], int lenggggggg) {\n    int i, j, SSN;\n')
 4print('This is CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC')
 5print('6L+Z5piv5L2g6KaB55qEZmxhZ+WQl++8nwpOU1NDVEZ7YjVlMzlkMDktODg3Yy1hZGI0LTE4OWMtMWI0OGEwNWJmOTY2fQ==')
 6A = {
 7    'flag': 'NSSCTF{a81c0d5e-ec6d-2b80' }
 8print('\n                SSN = FTC[j];\n                printf("flag");\n            }\n}\nint mian() {\n    int FTC[] = [ -,\n')
 9print('ccccccccccccccccccc')
10print('\nb, 2, 6, 7, -, d, 5, 8, 4, -, 6, 8, 7, 4, -, f, 1, 2, 6, e, 3, a, 5, 1, 0, 6, 1, }];\n    int lenggggggg = (int) sizeof(FTC) / sizeof(*FTC);\n    bnsScrt(FTC, lenggggggg);\n    int i;\n    for (i = 0; i < lenggggggg; i++)\n        printf("%d ", FTC[i]);\n    remake 0;\n}\n')
11print('----------------------------------------------------')
12import base64
13flag = '************************************'
14r = ''
15for x in range(len(flag)):
16    if (x + 1) % 4 == 0:
17        res = str(ord(chr(ord(flag[x]) ^ 2421)))
18    else:
19        res = str(ord(chr(ord(flag[x]) << 6 << 7 >> 2 >> 1 ^ 92)))
20    r = r + res + ','
21
22print(base64.b64encode(base64.b16encode(base64.b32encode(r.encode('utf-8')))).decode('utf-8'))

写出逆向脚本:

 1import base64
 2
 3c = 'NDc1NTMyNTQ0NzRGNDI1OTQ2NTEzMjU0NDc0RDVBNTU0NzQxNTc0NDQzNEQ0QTUzNDczNDVBNTQ0NTRDNDI1MzQ3NEQ1OTU0NEQ0QzQyNTI0NzQ1MzM1NDUxNEU0QTUzNDY1MTMyNDQ1MzRENTI1NTQ3NTE1NzQ0NEI0RDRBNTM0ODQ1NUE0MzU5NEQ1MjU0NDczNDM0NDM1OTRFNDI1NzQ3NDUzMzU0NDU0QzQyNTI0NzQxMzM1NDRENEQ0QTUzNDY1MTU5NTQ0MzRGNDI1OTQ3MzQzMzQzNTk0RDUyNTQ0NzQ5MzM0MzU5NEQ0QTUyNDczNDM0NDQ0QjRENTI0RDQ3NTEzMzQ0NDM0RTVBNTM0NjUxNTk1NDQ1NEQ0MjVBNDc0OTMyNDM1OTRENTI1NDQ3NEQ1OTQzNTk0RDRBNTI0NzU5MzQ0NDQ1NEY0MjRENDc0NTVBNDQ0NzRGNEE1QTQ3NTk1NzQ0NDk0RTUyNTI0NzM0NUE0MzU5NEQ1MjU0NDc0OTM0NTM1OTRENEE1MjQ4NDUzNDU0NDE0RDQyNEQ0NzQ1NTk1NDQ1NEU1QTU0NDc0OTU3NDQ0MzRENEE1MzQ3MzQ1QTU0NDU0QzQyNTM0NzREMzQ0NDRENEM0MjUyNDc0OTVBNTQ1MzRGNEE1NzQ2NTEzMjU0NDE0RDUyNTc0ODQxNTc0NDRCNEY0MjU1NDc1OTU5NDM1OTRENTI1NDQ3NDUzNDUzNTk0RDRBNTM0NzREMzQ1NDUzNEU1MjRENDc0NTU5NDQ0MzRFNDI1NzQ4NDE1NzQ0NDM0RDUyNTQ0ODQ1MzQ1NDRENEM0MjUzNDc0RDU5NTQ1MzRDNDI1NTQ4NDU1QTQ0NDk0RTQyNEQ0NzU1MzM1NDQ5NEQ1QTU3NDY1MTMyNTQ0RDRFNDI1MjQ3NDk1NzQ0NDU0RDVBNTQ0NzQ1NTc0MTNEM0QzRDNE'
 4
 5raw = base64.b32decode(base64.b16decode(base64.b64decode(c.encode('utf-8')))).decode('utf-8')
 6
 7flag = raw[:-1].split(',')
 8
 9for x in range(len(flag)):
10    if (x + 1) % 4 == 0:
11        print(chr(int(flag[x]) ^ 2421), end = '')
12    else:
13        print(chr((int(flag[x]) ^ 92) << 1 << 2 >> 7 >> 6), end = '')
14
15# output: 64nys02?-itcs-vory-lunn'y19zycyz087n

我的进度到这里就结束了,后来看 WP,要想得到正确的 flag 还有下面这一步

1>>> "".join([chr(ord('64nys02?-itcs-vory-lunn\'y19zycyz087n'[i]) ^ 0xA) if (i + 1) % 4 == 0 else '64nys02?-itcs-vory-lunn\'y19zycyz087n'[i] for i in range(len('64nys02?-itcs-vory-lunn\'y19zycyz087n'))])
2'64nss025-itis-very-funn-y19pycyp087d'

最遗憾的一集,比春秋杯爆破 Seed 还遗憾

攻防世界 - mfc逆向

程序加了 VMP 壳。

试图手撕,未果;试图使用工具脱壳,未果。遂不脱壳。

程序提示 "Flag就在控件里",以及窗口中间使用了文本框控件。使用 Spy++ 查看窗体句柄。

得到句柄 0xC208A。随后使用 xspy 分析窗口。

可以看到有一个自定义消息 0x0464 会触发地址为 002170 的函数。wparamlparam 不详就随便写一个试试。使用以下脚本尝试发送这个消息:

1#include <Windows.h>
2
3int main()
4{
5	HWND flag = HWND(0xC208A);
6	SendMessage(flag, 0x464, 114514, 1919810);
7}

发现窗口发生变化。

似乎有什么 DES 加密的地方,但是往后我就没辙了。看 WP 知道,"{I am a Des key}" 是 DES 加密的密钥,密文是这个窗口的类 "944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b",最终得到 flag

thIs_Is_real_kEy_hahaaa

但是我在尝试 DES 解密这一步里失败了,因为 DES 要求密钥为 8 字节,但是题目所给字符串有 16 字节,3DES 对偏移量有要求但是题目中并未给出。

西湖论剑 - easy_table

编程题,比赛结束前一个半小时开始看这道题,比赛结束后四个小时拿到 flag(当然中间还干别的事去了)而且应该是正确的,至少使用样例数据运行的结果是正确的。这个过程学到了相当多的东西,包括从头开始学了 pandas 模块、学习了 re 模块以及 Python 里其他巧妙的但我以前不知道的模块、函数和语法。由于一开始没有完全读懂题,并且我是第一次使用 pandas 模块,这个脚本有相当多可以优化的地方。题目给出待处理的数据总共有 10000 组,我的电脑运行下面的脚本需要亖分多钟:

  1import pandas as pd
  2import re
  3from datetime import datetime
  4import hashlib
  5
  6
  7def hash_calc(string):
  8    md5_hash = hashlib.md5()
  9    md5_hash.update(string.encode('utf-8'))
 10    return md5_hash.hexdigest()
 11
 12
 13def check_time(time_range, time_point):
 14    start, end = map(lambda x: datetime.strptime(x, '%H:%M:%S'), time_range.split('~'))
 15    point = datetime.strptime(time_point, '%H:%M:%S')
 16    return int(start <= point <= end)
 17
 18
 19def user_not_exist(user_id):
 20    if user_id in users['账号'].values:
 21        return 0
 22    else:
 23        return 1
 24
 25
 26def wrong_table(table_action, user_id):
 27    pattern = r"(from|update|insert into)\s+(\w+)\s" 
 28    matches = re.findall(pattern, table_action)
 29    table = matches[0][1]  # 查找表名
 30    
 31    for index_tables, row_tables in tables.iterrows(): 
 32        if row_tables['表名'] == table:
 33            table_id = row_tables['编号']  # 查找表编号
 34    
 35    for index_users, row_users in users.iterrows():
 36        if row_users['账号'] == user_id:
 37            group_id = row_users['所属权限组编号']  # 查找组编号
 38            uid = row_users['编号']  # 查找用户编号
 39
 40    for index_permissions, row_permissions in permissions.iterrows():
 41        if row_permissions['编号'] == group_id:
 42            operable_tables = row_permissions['可操作表编号'].split(',')  # 查找可操作表编号
 43
 44    if str(table_id) not in operable_tables:
 45        return [1, uid, group_id, table_id]
 46    else:
 47        return [0]
 48
 49
 50def wrong_operation(table_action, user_id):
 51    op_lst = ['insert', 'delete', 'update', 'select']
 52    pattern = r"(from|update|insert into)\s+(\w+)\s" 
 53    matches = re.findall(pattern, table_action)
 54    table = matches[0][1]  # 查找表名
 55    
 56    for op in op_lst:
 57        if op in table_action:
 58            operation = op  # 查找操作
 59            break
 60
 61    for index_users, row_users in users.iterrows():
 62        if row_users['账号'] == user_id:
 63            group_id = row_users['所属权限组编号']  # 查找组编号
 64            uid = row_users['编号']  # 查找用户编号
 65
 66    for index_permissions, row_permissions in permissions.iterrows():
 67        if row_permissions['编号'] == group_id:
 68            operable_permissions = row_permissions['可操作权限'].split(',')  # 查找可操作权限
 69
 70    for index_tables, row_tables in tables.iterrows(): 
 71        if row_tables['表名'] == table:
 72            table_id = row_tables['编号']  # 查找表编号
 73
 74    if operation not in operable_permissions:
 75        return [1, uid, group_id, table_id]
 76    else:
 77        return [0]
 78
 79
 80def wrong_time(action_time, user_id):
 81    time_point = action_time.split(' ')[1]
 82    pattern = r"(from|update|insert into)\s+(\w+)\s" 
 83    matches = re.findall(pattern, table_action)
 84    table = matches[0][1]  # 查找表名
 85    filt = []
 86
 87    for index_tables, row_tables in tables.iterrows():
 88        if row_tables['表名'] == table:
 89            time_lst = row_tables['可操作时间段(时:分:秒)'].split(',')
 90
 91    for index_users, row_users in users.iterrows():
 92        if row_users['账号'] == user_id:
 93            group_id = row_users['所属权限组编号']  # 查找组编号
 94            uid = row_users['编号']  # 查找用户编号
 95
 96    for index_tables, row_tables in tables.iterrows(): 
 97        if row_tables['表名'] == table:
 98            table_id = row_tables['编号']  # 查找表编号
 99
100    for time_range in time_lst:
101        filt.append(check_time(time_range, time_point))
102    
103    if 1 not in filt:
104        return [1, uid, group_id, table_id]
105    else:
106        return [0]
107
108
109actionlog = pd.read_csv("actionlog.csv")
110permissions = pd.read_csv("permissions.csv")
111tables = pd.read_csv("tables.csv")
112users = pd.read_csv("users.csv")
113
114raw_result = []
115
116for index_actionlog, row_actionlog in actionlog.iterrows(): 
117
118    user_id = row_actionlog['账号']
119    une = user_not_exist(user_id)  # 判断账号是否存在
120    if une:
121        raw_result.append([0, 0, 0, row_actionlog['编号']])
122        continue
123
124    table_action = row_actionlog['执行操作']
125    wt = wrong_table(table_action, user_id)  # 判断表是否可操作
126    if wt[0]:
127        wt.remove(wt[0])
128        wt.append(row_actionlog['编号'])
129        raw_result.append(wt)
130
131    wo = wrong_operation(table_action, user_id)
132    if wo[0]:
133        wo.remove(wo[0])
134        wo.append(row_actionlog['编号'])
135        raw_result.append(wo)
136
137    action_time = row_actionlog['操作时间']
138    wtime = wrong_time(action_time, user_id)
139    if wtime[0]:
140        wtime.remove(wtime[0])
141        wtime.append(row_actionlog['编号'])
142        raw_result.append(wtime)
143
144    percent = row_actionlog['编号'] / 100
145    print('\r', end='')
146    print(f'进度:{percent}%', end='')
147
148final_str = ''
149final_result = sorted(raw_result, key=lambda lst: (lst[0], lst[1], lst[2], lst[3]))
150for res in final_result:
151    final_str = final_str + '_'.join(list(map(str, res))) + ','
152final_str = final_str[:-1]
153print('\n' + final_str)
154print(hash_calc(final_str))

DASCTF{271b1ffebf7a76080c7a6e134ae4c929}

出了 WP,确实是对了。哈哈