首页
社区
课程
招聘
[原创] 第二题 南冥神功题解
2021-5-11 21:46 4119

[原创] 第二题 南冥神功题解

2021-5-11 21:46
4119

使用ida打开pzcrackme.exe, 跳转到主函数F5, 首先看到输入, 将其重命名为input;

 

image-1

 

之后进入循环:

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
  if ( strlen(input) <= 0x30 )
  {
    inpc = input[0];
    if ( input[0] )
    {
      ipos = 0;
      y = 0;
      tx = 0;
      length = dword_4B7020;
      keyc = a0123456789abcd[0];
next:
      if ( length > 0 )
      {
        keypos = 0;
        if ( keyc == inpc )
        {
LABEL_11:
        ...(此处暂时省略)
        }
        else
        {
          while ( length != ++keypos )
          {
            if ( a0123456789abcd[keypos] == inpc )
              goto LABEL_11;
          }
        }
      }
    }

主体逻辑是循环找到a0123456789abcd字符串中输入字符所在的位置keypos之后进入下面的逻辑, 分析见注释:

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
LABEL_11:
    v7 = (ipos + keypos / 6) % 6;
    v8 = keypos + ipos;
    x = tx;
    inp2 = v7;
    inp1 = 5 - v8 % 6; // 将keypos + ipos 按6进制分解为inp1和inp2
    for ( i = 0; ; i = 1 )
    {
    switch ( inp1 ) // 然后走迷宫
    {
        case 1:
            ++x;                            //
            break;
        case 2:
            v17 = (y++ & 1) == 0;           // 偶行 右下 奇行 下
            x += v17;
            break;
        case 3:
            v12 = (y++ & 1) != 0;           // 偶行 下 奇行 左下
            x -= v12;
            break;
        case 4:
            --x;                            //
            break;
        case 5:
            v19 = (y-- & 1) != 0;           // 偶行 上 奇行 左上
            x -= v19;
            break;
        default:                            // 偶行 右上 奇行 上
            v18 = (y-- & 1) == 0;
            x += v18;
            break;
    }
    if ( x > 9 ) // 迷宫有10
        break;
    if ( y > 8 ) // 迷宫有9
        break;
    v13 = &byte_4B7080[10 * y + x]; // 迷宫数据在byte_4B7080
    if ( *v13 )
        break;
    *v13 = 1; // 只能走在数据为0的格子, 走过的位置设为1
    if ( i == 1 )
    {
        ++ipos;
        tx = x;
        inpc = input[ipos];
        if ( inpc )
            goto next;
        goto fin;
    }
    inp1 = inp2;

之后是验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v14 = byte_4B7080; // 迷宫数据
v15 = 0;
do
{
    v16 = v14 + 0xA;
    do
        v15 += *v14++ == 0; // 如果迷宫数据中任意位是0 则v15会增加
    while ( v16 != v14 );
} while ( &unk_4B70DA != (_UNKNOWN *)v16 );
if ( !v15 ) // 当v15没有增加时 成功
{
    sub_4ABF30(&dword_4B8860, "Good job!", 9);
    sub_4AD980(&dword_4B8860);
    return 0;
}

所以就是要走迷宫把迷宫走成全是1的.

 

因为并没有分析它的一系列初始化函数, 怕哪里藏了坑然后静态的数据其实是骗你的, 所以接下来进行动态调试, 也为了绕过可能存在的反ida的调试措施(后来发现并没有) 所以使用frida进行动态调试.

 

根据分析结果写个frida agent脚本 (使用了typescript):

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
const PE = Process.findModuleByName("pzcrackme.exe");
 
// 输出迷宫
function show2d(data: NativePointer, width: number, height: number, showChar = false) {
    let text = "";
    const map = new Uint8Array(data.readByteArray(width*height));
    for(let y = 0; y < height; ++y) {
        for(let x = 0; x < width; ++x) {
            const chr = map[y*width + x];
            if(showChar) text += String.fromCharCode(chr);
            else text += ("00"+chr.toString(16)).substr(-2);
            text += " ";
        }
        text += "\n";
    }
    console.log(text);
}
 
function inspect() {
    // const keyLength = PE.base.add(0xB7020).readUInt();
    // const keyBuf = PE.base.add(0xB7040);
    // console.log(`keyLength: ${keyLength}`);
    // console.log(hexdump(keyBuf, {length: keyLength}));
    const mapBuf = PE.base.add(0xB7080);
    show2d(mapBuf, 10, 9);
}
 
// 根据每一步想要选择的选项0~5 逆向转换输入字符串
function convertInput(inp: string) {
    const keystr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    let i = 0, ki = 0, count = 0;
    let kx = 0, ky = 0;
    let result = '';
    for(let c of inp) {
        let target = c.charCodeAt(0) - '0'.charCodeAt(0);
        if(count%2 == 0) {
            // target = 5 - (i + kx) % 6
            kx = (5 - (target + i) % 6);
        } else {
            // target = (i + ky) % 6
            ky = (target + 6*100 - i) % 6;
            let key = ky*6 + kx;
            result += keystr[key];
        }
 
        count += 1;
        if(count%2 == 0) {
            i += 1;
        }
    }
    return result;
}
 
let myInput = convertInput("10");
 
Interceptor.attach(PE.base.add(0xB0AB0), { // 输入函数
    onEnter: function(args) {
        this.args = [];
        this.args[0] = ptr(args[0].toString());
        this.args[1] = ptr(args[1].toString());
    },
    onLeave: function(retVal) {
        inspect();
        (<NativePointer>this.args[1]).writeUtf8String(myInput); // 模拟输入
    }
});
 
Interceptor.attach(PE.base.add(0xABF30), { // 输出函数
    onEnter: function(args) {
        let logstr = args[1].readCString();
        console.log(logstr);
        if(logstr == 'Try again...') {
            inspect();
        }
    }
});

运行后输出如下:

 

image-2

 

起始在左上角, 可以自由向左右上下走, (从0开始算)在偶数行可以向右上或右下走, 在奇数行可以向左上或左下走, 可以手走一遍出结果, 为: convertInput("1234321234321101210050543450501210121234322321")

 

将其输出出来, 最后flag是GJ0V4LA4VKEVQZSVCNGJ00N


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

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