[ACTF新生赛2020]easyre

打开压缩包,一个EXE文件,使用PIE发现使用UPX加固,用upx -d脱壳之后加载到IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _BYTE v4[12]; // [esp+12h] [ebp-2Eh] BYREF
  _DWORD v5[3]; // [esp+1Eh] [ebp-22h]
  _BYTE v6[5]; // [esp+2Ah] [ebp-16h] BYREF
  int v7; // [esp+2Fh] [ebp-11h]
  int v8; // [esp+33h] [ebp-Dh]
  int v9; // [esp+37h] [ebp-9h]
  char v10; // [esp+3Bh] [ebp-5h]
  int i; // [esp+3Ch] [ebp-4h]

  __main();
  qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
  printf("Please input:");
  scanf("%s", v6);
               'A'            'C'            'T'              'F'             '{'           '}'
  if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v10 != 125 )
    return 0;
  v5[0] = v7;
  v5[1] = v8;
  v5[2] = v9;
  for ( i = 0; i <= 11; ++i )
  {
    if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
      return 0;
  }
  printf("You are correct!");
  return 0;
}

直接看伪代码,下意识把伪代码当真正代码分析,发现根本不知道v10,v7,v8,v9是什么,v10根据"}"可以猜测是Flag最后一位

还是看看汇编顺便学习一下指令

.text:00401340 _main           proc near               ; CODE XREF: ___mingw_CRTStartup+F8↑p
.text:00401340
.text:00401340 anonymous_0     = dword ptr -11h
.text:00401340 anonymous_1     = dword ptr -0Dh
.text:00401340 anonymous_2     = dword ptr -9
.text:00401340 anonymous_3     = byte ptr -5
.text:00401340 anonymous_4     = dword ptr -4
.text:00401340 argc            = dword ptr  8
.text:00401340 argv            = dword ptr  0Ch
.text:00401340 envp            = dword ptr  10h
.text:00401340
.text:00401340                 push    ebp
.text:00401341                 mov     ebp, esp
.text:00401343                 and     esp, 0FFFFFFF0h ; Logical AND
.text:00401346                 sub     esp, 40h        ; Integer Subtraction
.text:00401349 ; 11:   __main();
.text:00401349                 call    ___main         ; Call Procedure
.text:0040134E ; 12:   qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
.text:0040134E                 mov     byte ptr [esp+12h], 2Ah ; '*'
.text:00401353 ; 13:   printf("Please input:");
.text:00401353                 mov     byte ptr [esp+13h], 46h ; 'F'
.text:00401358                 mov     byte ptr [esp+14h], 27h ; '''
.text:0040135D                 mov     byte ptr [esp+15h], 22h ; '"'
.text:00401362                 mov     byte ptr [esp+16h], 4Eh ; 'N'
.text:00401367                 mov     byte ptr [esp+17h], 2Ch ; ','
.text:0040136C                 mov     byte ptr [esp+18h], 22h ; '"'
.text:00401371                 mov     byte ptr [esp+19h], 28h ; '('
.text:00401376                 mov     byte ptr [esp+1Ah], 49h ; 'I'
.text:0040137B                 mov     byte ptr [esp+1Bh], 3Fh ; '?'
.text:00401380                 mov     byte ptr [esp+1Ch], 2Bh ; '+'
.text:00401385                 mov     byte ptr [esp+1Dh], 40h ; '@'
.text:0040138A                 mov     dword ptr [esp], offset Format ; "Please input:"
.text:00401391                 call    _printf         ; Call Procedure
.text:00401396 ; 14:   scanf("%s", v6);
.text:00401396                 lea     eax, [esp+2Ah]  ; Load Effective Address
.text:0040139A                 mov     [esp+4], eax
.text:0040139E                 mov     dword ptr [esp], offset aS ; "%s"
.text:004013A5                 call    _scanf          ; Call Procedure
.text:004013AA ; 15:   if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v10 != 125 )
.text:004013AA                 movzx   eax, byte ptr [esp+2Ah] ; Move with Zero-Extend
.text:004013AF                 cmp     al, 41h ; 'A'   ; 如果相等结果为0,ZF为1
.text:004013B1                 jnz     short loc_4013E0 ; jnz, 结果不为零跳/ZF=0跳/不相等跳
                            ; jz对应相等就跳 jnz对应不相等就跳
.text:004013B3                 movzx   eax, byte ptr [esp+2Bh] ; Move with Zero-Extend
.text:004013B8                 cmp     al, 43h ; 'C'   ; Compare Two Operands
.text:004013BA                 jnz     short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013BC                 movzx   eax, byte ptr [esp+2Ch] ; Move with Zero-Extend
.text:004013C1                 cmp     al, 54h ; 'T'   ; Compare Two Operands
.text:004013C3                 jnz     short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013C5                 movzx   eax, byte ptr [esp+2Dh] ; Move with Zero-Extend
.text:004013CA                 cmp     al, 46h ; 'F'   ; Compare Two Operands
.text:004013CC                 jnz     short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013CE                 movzx   eax, byte ptr [esp+2Eh] ; Move with Zero-Extend
.text:004013D3                 cmp     al, 7Bh ; '{'   ; Compare Two Operands
.text:004013D5                 jnz     short loc_4013E0 ; Jump if Not Zero (ZF=0)
.text:004013D7                 movzx   eax, byte ptr [esp+3Bh] ; Move with Zero-Extend
.text:004013DC                 cmp     al, 7Dh ; '}'   ; Compare Two Operands
.text:004013DE                 jz      short loc_4013E7 ; jz, 结果为0跳/ZF=1跳
.text:004013E0 ; 16:     return 0;
.text:004013E0
.text:004013E0 loc_4013E0:                             ; CODE XREF: _main+71↑j
.text:004013E0                                         ; _main+7A↑j ...
.text:004013E0                 mov     eax, 0
.text:004013E5                 jmp     short locret_40145B ; Jump
.text:004013E7 ; ---------------------------------------------------------------------------
.text:004013E7 ; 17:   v5[0] = v7;
.text:004013E7
.text:004013E7 loc_4013E7:                             ; CODE XREF: _main+9E↑j
.text:004013E7                 lea     eax, [esp+2Ah]  ; Load Effective Address 取了输入字符串的首地址, [esp+2Ah]是上面我们输入字符的首地址
.text:004013EB                 add     eax, 5          ; Add 加5,相当于地址加5,理解为下标加5
                        ; ['a', 'b', 'c', 'd', 'e', 'f']         ['a', 'b', 'c', 'd', 'e', 'f'] 
                        ;   ^                                                              ^
.text:004013EE                 mov     edx, [eax]        ;取值复制给edx
.text:004013F0                 mov     [esp+1Eh], edx
.text:004013F4 ; 18:   v5[1] = v8;
.text:004013F4                 mov     edx, [eax+4]        ;移动一个字符(4bit)
.text:004013F7                 mov     [esp+22h], edx    ;保存的数据也移动4bit
.text:004013FB ; 19:   v5[2] = v9;
.text:004013FB                 mov     eax, [eax+8]
.text:004013FE                 mov     [esp+26h], eax
.text:00401402 ; 20:   for ( i = 0; i <= 11; ++i )
.text:00401402                 mov     dword ptr [esp+3Ch], 0 # 循环计数器
.text:0040140A                 jmp     short loc_401443 ; Jump
.text:0040140C ; ---------------------------------------------------------------------------
.text:0040140C ; 22:     if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
.text:0040140C
.text:0040140C loc_40140C:                             ; CODE XREF: _main+108↓j
.text:0040140C                 lea     edx, [esp+12h]  ; key = "*F'\"N,\"(I?+@" 首地址, edx保存首个字符地址
.text:00401410                 mov     eax, [esp+3Ch]
.text:00401414                 add     eax, edx        ; Add   &key + i == &k + [esp+3Ch] 
.text:00401416                 movzx   edx, byte ptr [eax] ; Move with Zero-Extend 保存下标为[esp+3Ch]/i的key中的元素
.text:00401419                 lea     ecx, [esp+1Eh]  ; Load Effective Address 输入字符串的首地址
.text:0040141D                 mov     eax, [esp+3Ch]  ; 源代码中的i变量保存到eax
.text:00401421                 add     eax, ecx        ; 字符串首地址+i, 也就是输入字符串中第i个字符的地址
.text:00401423                 movzx   eax, byte ptr [eax] ; Move with Zero-Extend 取输入字符串中的i个字符的ASCII到eax,0填充
.text:00401426                 movsx   eax, al         ; Move with Sign-Extend 符号扩展填充
.text:00401429                 sub     eax, 1          ; Integer Subtraction 输入字符串中第i个字符ASCII值减1
.text:0040142C                 movzx   eax, __data_start__[eax] ; Move with Zero-Extend 在__data_start__中取下标为(输入字符串中第i个字符ASCII值减1)值的字符
.text:00401433                 cmp     dl, al          ; Compare Two Operands 再与key取中取出的值比较
.text:00401435                 jz      short loc_40143E ; Jump if Zero (ZF=1)
.text:00401437 ; 23:       return 0;
.text:00401437                 mov     eax, 0
.text:0040143C                 jmp     short locret_40145B ; Jump
.text:0040143E ; ---------------------------------------------------------------------------
.text:0040143E
.text:0040143E loc_40143E:                             ; CODE XREF: _main+F5↑j
.text:0040143E                 add     dword ptr [esp+3Ch], 1 ; Add
.text:00401443
.text:00401443 loc_401443:                             ; CODE XREF: _main+CA↑j
.text:00401443                 cmp     dword ptr [esp+3Ch], 0Bh ; Compare Two Operands
.text:00401448                 jle     short loc_40140C ; Jump if Less or Equal (ZF=1 | SF!=OF)
.text:0040144A ; 25:   printf("You are correct!");
.text:0040144A                 mov     dword ptr [esp], offset aYouAreCorrect ; "You are correct!"
.text:00401451                 call    _printf         ; Call Procedure
.text:00401456                 mov     eax, 0
.text:0040145B ; 26:   return 0;
.text:0040145B
.text:0040145B locret_40145B:                          ; CODE XREF: _main+A5↑j
.text:0040145B                                         ; _main+FC↑j
.text:0040145B                 leave                   ; High Level Procedure Exit
.text:0040145C                 retn                    ; Return Near from Procedure
.text:0040145C _main           endp

MOVZXMOVSX

扩展传送,无符号数或正数高位扩展为0,负数高位扩展为全1。汇编语言MOVZX和MOVSX指令

如果一个十六进制常数的最大有效数字大于 7,那么它的最高位等于 1。如下例所示,传送到 BX 的十六进制数值为 A69B,因此,数字“A”就意味着最高位是 1。(A69B 前面的 0 是一种方便的表示法,用于防止汇编器将常数误认为标识符。)

MOVSX eax, bx   ; 当源操作数的空间小于目的操作数

例如源操作数为32位,目的操作数为16位

指令MOVSX先判断空间小的操作数的符号位(最高位)

  • 如果bx最高位为1,即用1填充
  • 如果bx为正数,则用0填充与MOVZX
;eax  -> 00000000
;bx   -> 0000A123
MOVSX eax, bx
;eax  -> 1111A123

解题代码

key = '''*F'\"N,\"(I?+@'''
mapp = r'''~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$# !"'''

flag = ""
for i in key:
    index_c = mapp.find(i) + 1
    flag += chr(index_c)
print("ACTF{" + flag + "}")
# ACTF{U9X#_1S#_W6@T?}

maze

新手迷宫题,拖进IDA,F5大法

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 v3; // rbx
  int v4; // eax
  bool v5; // bp
  bool v6; // al
  const char *v7; // rdi
  unsigned int v9; // [rsp+0h] [rbp-28h] BYREF
  int v10[9]; // [rsp+4h] [rbp-24h] BYREF

  v10[0] = 0;                                   // 入口,起始位置 0,0
  v9 = 0;
  puts("Input flag:");
  scanf("%s", &s1);
  if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )
      // 判断输入字符串长度是否为24, 前五个字符是否为'nctf{', 最后一个字符是否为"}" 
  {
LABEL_22:
    puts("Wrong flag!");
    exit(-1);
  }
  v3 = 5LL;
  if ( strlen(&s1) - 1 > 5 )
  {
    while ( 1 )
    {
      v4 = *(&s1 + v3); // 输入的字符串首地址加v3(5LL), 也就是跳过输入字符串前五个字符`nctf{`
      v5 = 0;
      if ( v4 > 78 )
      {
        if ( (unsigned __int8)v4 == 'O' )    // 如果当前字符为'O'
        {
          v6 = sub_400650(v10);                 // 减一
          goto LABEL_14;
        }
        if ( (unsigned __int8)v4 == 'o' )    // 如果当前字符为'o'
        {
          v6 = sub_400660(v10);                 // 加一
          goto LABEL_14;
        }
      }
      else
      {
        if ( (unsigned __int8)v4 == '.' )    // 如果当前字符为'.'
        {
          v6 = sub_400670(&v9);                 // 减一
          goto LABEL_14;
        }
        if ( (unsigned __int8)v4 == '0' )    // 如果当前字符为'0'
        {
          v6 = sub_400680((int *)&v9);          // 加一
LABEL_14:
          v5 = v6;
          goto LABEL_15;
        }
      }
LABEL_15:
      if ( !(unsigned __int8)sub_400690((__int64)asc_601060, v10[0], v9) ) // 将asc_601060 v10 v9的值传进函数
        goto LABEL_22;
      if ( ++v3 >= strlen(&s1) - 1 )  // 判断是否判断完所有输入的字符
      {
        if ( v5 )
          break;
LABEL_20:
        v7 = "Wrong flag!";
        goto LABEL_21;
      }
    }
  }
  if ( asc_601060[8 * v9 + v10[0]] != '#' )     // 8 * v9代表行,因为迷宫的字符都是一行,每八个一行所以
                                                // 1*8代表第一行
                                                // 2*8代表第二行
                                                // 然后v10代表列
                                                // v9,行加减一代表上下,加为下,减为上
                                                // v10,列加减一代表左右,加为右,减为左
    goto LABEL_20;
  v7 = "Congratulations!";
LABEL_21:
  puts(v7);
  return 0LL;
}

跟进每个函数

// v4 = 'O'
bool __fastcall sub_400650(_DWORD *a1){
  int v1; // eax
  v1 = (*a1)--;
  return v1 > 0;
  // 将传进的值减一 判断是否大于0
}

// v4 = 'o'
bool __fastcall sub_400660(int *a1){
  int v1; // eax
  v1 = *a1 + 1;
  *a1 = v1;
  return v1 < 8;
  // 将传进的值加一 判断是否小于8
}

// v4 = '.'
bool __fastcall sub_400670(_DWORD *a1){
  int v1; // eax
  v1 = (*a1)--;
  return v1 > 0;
  // 将传进的值减一 判断是否大于0
}

// v4 = '0'
bool __fastcall sub_400680(int *a1){
  int v1; // eax
  v1 = *a1 + 1;
  *a1 = v1;
  return v1 < 8;
  // 将传进的值加一 判断是否小于8
}

// 得出最终的v9 和 v10
// asc_601060 =   '*******   *  **** * ****  * ***  *#  *** *** ***     *********'  // 迷宫字符
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3){
  __int64 result; // rax
  result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);  // 通过行和列计算当前位置是否为' '和'#'
                            // 与常数相乘的变量大多为 行,另一个则为列
  LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
  return result;

得出这是一个8*8的迷宫,根据打印asc_601060

# 方便观察将空格替换为1, *替换为0
11000000
01110110
00010100
00110100
0110#110
00100010
00111110
00000000

根据判断,v9代表行,则v9加减一代表向下、上走一步,v10代表列,则v10加减一代表向右、左走一步

. 往上
0 往下
O 往左
o 往右

找出一条路径到达#并且长度为18,因为总长度24,减去nctf{},剩下18个字符串,手动走一下

o0oo00O000oooo..OO
nctf{o0oo00O000oooo..OO}

Snipaste_2021-07-23_09-14-50

对于一些复杂的迷宫找路径,可以使用BFS广度优先算法

// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <queue>
#include <string>
using namespace std;

struct point
{
    // 自定义结构体 横纵坐标 步数或者符号
    int x, y;
    // int step;
    string step;
};

//     迷宫         标志是否遍历过
int map[100][100], v[100][100] = { 0 };

//  行数  列数    起点x轴  起点y轴 终点x轴 终点y轴
int row, column, startx, starty, endx, endy;
// 顺时针探索下一个点 方向
/*int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
*/
int dd[4][1]{ {0,1}, {1,0}, {0,-1}, {-1,0} };

// 右下左上操作符号
char s[4] = { 'R','D','L','U' };



queue<point> Q;


int BFS() {
    // 广度优先算法

    // 初始起点
    point start;
    start.x = startx;
    start.y = starty;
    // start.step = 1;
    start.step = "";


    // 是否找到最优
    int flag = 0;

    // 起点入队列
    Q.push(start);
    v[startx][starty] = 1; // 表示已经遍历过

    while (!Q.empty()) {
        int x = Q.front().x, y = Q.front().y;

        // 判断当前是否在终点
        if (x == endx && y == endy) { // 判断终点的条件 可以为字符或者坐标

            flag = 1;
            printf("%s", Q.front().step.c_str());
            // printf("%s", str.c_str());
        }

        for (int k = 0; k < 4; k++) {
            int tx, ty;
            tx = x + dd[k][0];
            ty = y + dd[k][2];
            if (0 <= tx && tx < row && 0 <= ty && ty < column) {
                if (map[tx][ty] == 1 && v[tx][ty] == 0) {
                    // 如方向点的坐标可行并且未遍历过则入队列
                    point tmp;
                    tmp.x = tx;
                    tmp.y = ty;
                    tmp.step = Q.front().step + s[k];    // 拼接字符模式
                    // tmp.step = Q.front().step + 1;    // 计算步数模式
                    Q.push(tmp);
                    v[tx][ty] = 1;
                }
            }

        }
        Q.pop(); // 拓展就将首元素出队列

    }

    return flag;
}

int main()
{
/*
    5 4
    1 1 2 1
    1 1 1 1
    1 1 2 1
    1 2 1 1
    1 1 1 2
    1 1 3 3

    8 8
    1 1 0 0 0 0 0 0
    0 1 1 1 0 1 1 0
    0 0 0 1 0 1 0 0
    0 0 1 1 0 1 0 0
    0 1 1 0 1 1 1 0
    0 0 1 0 0 0 1 0
    0 0 1 1 1 1 1 0
    0 0 0 0 0 0 0 0
    0 0 4 4
*/

    // 输入行数和列数 小于100*100
    scanf_s("%d%d", &row, &column);

    // 输入迷宫数据
    int flag = 0;
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < column; j++)
            scanf_s("%d", &map[i][j]);
    }

    // 输入起点和终点
    scanf_s("%d%d%d%d", &startx, &starty, &endx, &endy);

    // BFS
    int res = BFS();
    if (res == 0) printf("no");
    return 0;
}

参考

标签: RE

添加新评论