首页
社区
课程
招聘
21kctf秋:第二题 迷失丛林
2021-11-18 05:09 17786

21kctf秋:第二题 迷失丛林

2021-11-18 05:09
17786

21kctf秋

第二题 迷失丛林

摘要

这是个有点脑洞的题,通过一个0x100字节的表构建一个0x200字节的表,再根据0x200字节的表构造一个0x10000字节的表,然后检查0x10000的表。接着是一个二叉树,处理输入后与明文字符串做比较。

 

0x100字节的表前8字节由输入决定,如果直接爆破是2的64次方,但实际上这个表中0~255出现的次数都只有一次,所以爆破减少到8的8次方

 

后面的二叉树依次处理输入的后半段,单个字节爆破是2的8次方

初步分析

查找字符串TryAgain会发现旁边有个GoodJob~

 

然后查看字符串交叉引用,找到目标函数在0x401270(TryAgain)

 

0x401270会检测输入长度,然后做一些初始化,进入GoodJob~所在的0x401580

 

输入的字符串长度是32

 

sub_4014A0是个16进程字符串转整形的函数,且字符串是小端存储的

校验1

0x100的那个表是根据输入前半段构造的,与后半段无关

 

首先爆破0x100的那个表,直接复制反编译代码,然后用个dfs爆即可,这里没有做剪枝,会慢一点:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
bool Gen()
{
    memset(mm10000, 0, 0x10000);
 
    byte sums[4];
    *(dword*)sums = 0;
    int idxNext = 1;
    byte* p10000 = mm10000;
    do {
        mm200[0] = mm[idxNext - 1];                 // Initmm200
        mm200[1] = idxNext;
        byte*  p200a = mm200;
        int* ppow = pown1s;
        byte* p200b = &mm200[pown1s[0]];                  // p200b = &mm200[2];
        do                                          // Setmm200
        {
            int p = *ppow;                                // p = 2^(i+1) = [2, 4, 8, 0x10, ...]
            if (*ppow > 0) {
                do {
                    byte* p200c = p200b + 1;
                    *(p200c - 1) = mm[(unsigned __int8)*p200a];// *(p200b) = mm[*p200a]
                    *p200c = *p200a + 1;                  // *(p200b + 1) = *p200a + 1
                    p200b = p200c + 1;                    // p200b += 2
                    ++p200a;
                    --p;
                } while (p);
            }
            ++ppow;
        } while ((int)ppow < (int)&pown1s[7]);      // 254 times
        int v8 = 256;                                   // Setmm10000
        do {
            ++p10000[(unsigned __int8)*p200a++];
            --v8;
        } while (v8);
        ++idxNext;
        p10000 += 256;
    } while (idxNext - 1 < 256);
    byte* v9 = &mm10000[40];
    int i_step = 256;
    do {
        if (*(v9 - 40))                           // 0
            ++sums[0];
        if (*(v9 - 26))                           // 14
            ++sums[1];
        if (*v9)                                  // 40
            ++sums[2];
        if (v9[39])                               // 79
            ++sums[3];
        v9 += 256;
        --i_step;
    } while (i_step);
    // sums[0] == (char)0xA9 && sums[1] == (char)0xAC && sums[2] == (char)0xA7 && sums[3] > 0xC8u
    if (sums[0] == 0xA9 && sums[1] == 0xAC && sums[2] == 0xA7) {
        cout << "sums[3]" << (int)sums[3] << endl;
        return true;
    }
    else {
        // cout << (int)sums[0] << " " << (int)sums[1] << " " << (int)sums[2] << " " << (int)sums[3] << endl;
        return false;
    }
}
 
void dfs(int layer)
{
    if (layer == 8) {
        if (Gen()) {
            for (int i = 0; i < 8; i++)
                cout << (int)mm[i] << " ";
            cout << endl;
        }
        return;
    }
 
    for (int i = 0; i < 8; i++) {
        if (flag[i])
            continue;
        mm[layer] = prob[i];
        flag[i] = 1;
        dfs(layer + 1);
        flag[i] = 0;
    }
}

剪枝的思路是提前判断代码中sums的值,如果sums+剩余次数小于目标值或sums大于目标值则可以提前退出

 

这段代码实际上是根据0x100生成一个0x200的表(实际上是0x1fe),再统计0x200表后0x100项中4个数字的出现次数,如果出现就在sums中记录,最后检查sums

 

下面是化简过后的代码:

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
for i in range(256):
    mm10000[0] = 0
    mm10000[14] = 0
    mm10000[40] = 0
    mm10000[79] = 0
 
    # mm200[0:510] = f()
    mm200[0] = mm[i]
    mm200[1] = i + 1
 
    for j in range(1, 255): # j-1 = [0, 254)
        mm200[j*2] = mm[mm200[j-1]]
        mm200[j*2+1] = mm200[j-1] + 1
 
    # mm10000[i*256+0:(i+1)*256] = f(mm200[254:510])
    for j in range(254, 254 + 256):
        mm10000[mm200[j]] = 1 # ++p10000[*p200a++];
 
    if mm10000[0] != 0 :
        sums[0] += 1
    if mm10000[14] != 0 :
        sums[1] += 1
    if mm10000[40] != 0 :
        sums[2] += 1
    if mm10000[79] != 0 :
        sums[3] += 1

校验2

检查完sums后,0x100那个表会发生一些变化,这个变化只与表自身的值有关,所以在通过校验1得到正确的值后,动态调试dump出变化后的结果即可

 

接着有一个两层循环,化简代码后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# a2是输入后半段
 
resultStr = [mm[i] for i in range(8)]
 
for i_bit in range(0, 8):
    for i_ord in range(0, 8):
        if((a2[i_ord] & 1) != 0): # if a2[i2] 最低位为1
            resultStr[i_ord] += 1;
        else:
            resultStr[i_ord] = mm[resultStr[i_ord]];
        a2[i_ord] >>= 1;
 
i_bit = 8
--resultStr[0];
--resultStr[7];
 
if resultStr == 'GoodJob~':
    print('GoodJob~')

易得出前面两个循环结束后resultStr=b'HoodJob\x7f'

 

而那两个循环可以通过如下代码爆破:

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
bool Check(byte b, byte target, int step)
{
    if (step == 8) {
        if (b == target) {
            byte p = 0;
            for (int i = 7; i >= 0; i--) {
                p <<= 1;
                p |= path[i];
            }
            cout << (int)p << ", ";
 
            return true;
        }
        return false;
    }
 
    path[step] = 0;
    if (Check(mm2[b], target, step + 1))
        return true;
 
    path[step] = 1;
    if (Check(b+1, target, step + 1))
        return true;
 
    return false;
}

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回