Back
Featured image of post Reverse from 0 to 1

Reverse from 0 to 1

从 BUUOJ 入门 Reverse

内涵的软件

首先使用file命令,发现exe是32位的,将其拖入ida中

进入main函数

点击进入main0函数

可以看到一串v2字符串

结合程序运行时出现的文字:”这里本来应该是答案的“

猜测v2就是答案

直接提交

发现不对

将开头修改为flag

提交后直接通过

新年快乐

首先使用file命令查看exe文件信息,发现是32位

尝试运行发现是要求输入flag

拖入ida进行静态分析

发现只有一个start函数,ida各种报错,无法分析

猜测使用了upx壳

使用exeinfope软件进行查看,upx壳实锤

脱壳后再次拖入ida

发现v4为"HappyNewYear!"

对于输入v5,直接与v4进行strncmp的比较

因此只需要与v4相同即可

尝试后发现没问题

套上flag提交

成功

guessgame

使用软件,发现是一个猜数游戏

拖入ida分析

发现整个代码与flag没有任何关系,就是一个猜测随机数的游戏

于是猜测flag隐藏在字符串常量中

进入字符串常量最顶部,发现如下字符串:

BJD{S1mple_ReV3r5e_W1th_0D_0r_IDA}

即为flag

helloword

下载后发现是apk文件

首先将apk后缀更改为zip

解压后发现文件夹中含有一个classes.dex文件

使用dex2jar软件将其转换为jar文件

使用jd-gui.jar对其进行逆向分析

在源代码中有com.example.helloword文件夹,用过Java的都知道com.example是什么东西,因此进入该目录下,发现有个MainActivity.class文件,查看源代码,发现有个flag字符串并对其使用了一个比较方法(compareTo),猜测这个就是需要的flag,提交后发现正确

xor

拖入ida进行静态分析,发现对输入进行了异或处理,处理结果符合某个特定的函数值即可求解

看到一个for循环,是对每个字符与前面一个字符进行异或

根据异或的性质可知,只需要再异或一次就能还原

所以从后往前进行异或

随便写个脚本计算一下:

#include <stdio.h>

int main(){
    char flag[35];
    flag[0] = 'f';
    flag[1] = '\n';
    flag[2] = 'k';
    flag[3] = '\f';
    flag[4] = 'w';
    flag[5] = '&';
    flag[6] = 'O';
    flag[7] = '.';
    flag[8] = '@';
    flag[9] = '\x11';
    flag[10] = 'x';
    flag[11] = '\r';
    flag[12] = 'Z';
    flag[13] = ';';
    flag[14] = 'U';
    flag[15] = '\x11';
    flag[16] = 'p';
    flag[17] = '\x19';
    flag[18] = 'F';
    flag[19] = '\x1F';
    flag[20] = 'v';
    flag[21] = '"';
    flag[22] = 'M';
    flag[23] = '#';
    flag[24] = 'D';
    flag[25] = '\x0E';
    flag[26] = 'g';
    flag[27] = 6;
    flag[28] = 'h';
    flag[29] = '\x0F';
    flag[30] = 'G';
    flag[31] = '2';
    flag[32] = 'O';
    flag[33] = 0;
    for (int i = 32; i >= 1; --i) {
        flag[i] ^= flag[i - 1];
    }
    printf("%s", flag);
    return 0;
}

// flag{QianQiuWanDai_YiTongJiangHu}

reverse3

进行初步检查后拖入ida进行静态分析

发现对于输入内容使用了一个函数进行变换

查看字符串发现使用了一个"ABCD…+/=“的字符串,猜测使用了BASE64编码

回到主函数,发现使用了一个str2与base64后的结果进行比较

查看字符串可知:str2=e3nifIH9b_C@n@dH

明显不是base64的结果

回到主函数发现对base64结果进行了加法操作

编程进行反向操作后得到:e2lfbDB2ZV95b3V9

使用在线工具可得解码结果:{i_l0ve_you}

套上flag提交,通过

不一样的flag

首先测试一下软件

发现好像是个前进后退的游戏

猜测是一个迷宫

进入ida静态分析

发现当v8[x]==35时为成功v8[x]==49时失败

查阅ASCII发现35对应#,49对应1因此查找同时含有这两个字符的字符串:

*11110100001010000101111#

总长度为24,与5*v4+v5的最大值一致,因此忽略-41

要求是不能进入1的位置而到达#

因此进行简单的推导即可得到要求的序列(完全可以写个代码跑一下,但由于这个题比较简单,直接手算即可)

得到输入序列:222441144222

套上flag提交即可

注:此题可以看作是一个5*5的迷宫:

* 1 1 1 1
0 1 0 0 0
0 1 0 1 0
0 0 0 1 0
1 1 1 1 #

1是障碍,0是可以走的路,*是起点,#是重点

这样就与运行程序时的 up, down, left, right 对应上了

SimpleRev

拖入ida

发现这道题涉及到类型转换,需要将int转换为char插入到字符串后面

需要注意的是在转换时要把顺序倒过来

转换后text为killshadow,key为ADSFKNDCLS

随后将key转换为小写字母,即adsfkndcls

编程枚举求解

int main{
    char text[25];
    strcpy(text, "killshadow");
    char key[25];
    strcpy(key, "adsfkndcls");
    char str2[25];
    int textlen = strlen(text);
    for (int i = 0; i < textlen; ++i) {
        for (char c = 'A'; c <= 'z'; ++c) {
            if (c <= 96 || c > 122) {
                if (c > 64 && c <= 90) {
                    if(text[i] == (c - 39 - key[i] + 97) % 26 + 97) {
                        printf("%d%c ", i, c);
                    }
                }
            }
            else {
                if(text[i] == (c - 39 - key[i] + 97) % 26 + 97) {
                    printf("%d%c ", i, c);
                }
            }
        }
    }
    return 0;
}

每个位置均可以算出两个解,一个是大写字母,一个是小写字母

全部挑选大写字母后提交,可以通过

想尝试一下其它组合方案是否也可以,但可惜提交平台不允许多次提交

Java逆向解密

将.class文件拖入jd-gui.jar中进行逆向

可以得到Reverse.class源代码:

import java.util.ArrayList;
import java.util.Scanner;

public class Reverse {
  public static void main(String[] args) {
    Scanner s = new Scanner(System.in);
    System.out.println("Please input the flag );
    String str = s.next();
    System.out.println("Your input is );
    System.out.println(str);
    char[] stringArr = str.toCharArray();
    Encrypt(stringArr);
  }
  
  public static void Encrypt(char[] arr) {
    ArrayList<Integer> Resultlist = new ArrayList<>();
    for (int i = 0; i < arr.length; i++) {
      int result = arr[i] + 64 ^ 0x20;
      Resultlist.add(Integer.valueOf(result));
    } 
    int[] KEY = { 
        180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 
        133, 191, 134, 140, 129, 135, 191, 65 };
    ArrayList<Integer> KEYList = new ArrayList<>();
    for (int j = 0; j < KEY.length; j++)
      KEYList.add(Integer.valueOf(KEY[j])); 
    System.out.println("Result:");
    if (Resultlist.equals(KEYList)) {
      System.out.println("Congratulations);
    } else {
      System.err.println("Error);
    } 
  }
}

这段代码就是将输入的每个字符加64后与0x20进行异或

因此使用python进行暴力破解(其实完全可以反向计算就出来的,当时大意了)

key = [180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
for k in key:
    for c in range(0x21, 0x80):
        if (c + 64) ^ 0x20 == k:
            print chr(c)
# This_is_the_flag_!

[WUSTCTF2020]Crossfun

拖入ida,发现有个判断函数,里面对输入的每个字符进行了判断,把所有判断函数整合起来,就得到flag了

flag: wctf2020{cpp_@nd_r3verse_@re_fun}

[WUSTCTF2020]level1

拖入ida

stream = fopen("flag", "r");
fread(ptr, 1uLL, 0x14uLL, stream);
fclose(stream);
for ( i = 1; i <= 19; ++i ) {
    if ( i & 1 )
        printf("%ld\n", (unsigned int)(ptr[i] << i));
    else
        printf("%ld\n", (unsigned int)(i * ptr[i]));
}

显然 ptr 里面就是flag

看到 output.txt 里面正好有19行

对其进行逆变换(注意0)

ptr = [0, 198, 232, 816, 200, 1536, 300, 6144, 984, 51200, 570, 92160, 1200, 565248, 756, 1474560, 800, 6291456, 1782, 65536000]
flag = ''
for i in range(1, 20):
    if i % 2 == 1:
        flag += chr(ptr[i] >> i)
    else:
        flag += chr(ptr[i] // i)

print (flag)

得到flag ctf2020{d9-dE6-20c}

[WUSTCTF2020]level2

先使用upx进行脱壳

upx -d attachment

进入main函数

还没来得及按 tab 前就看到了flag

; __unwind {
lea     ecx, [esp+4]
and     esp, 0FFFFFFF0h
push    dword ptr [ecx-4]
push    ebp
mov     ebp, esp
push    ecx
sub     esp, 14h
mov     [ebp+var_C], offset flag ; "wctf2020{Just_upx_-d}"
sub     esp, 0Ch
push    offset aWhereIsIt ; "where is it?"
call    puts
add     esp, 10h
mov     eax, 0
mov     ecx, [ebp+var_4]
leave
lea     esp, [ecx-4]
retn
; } // starts at 804887C

这件事情告诉我们还是要看汇编代码的

flag{Just_upx_-d}

[WUSTCTF2020]level3

首先尝试运行程序,发现是一个base64编码程序

进行测试后看到程序中输出一个字符串 Is there something wrong?

推测这个base64可能不正确

使用在线工具测试后发现大写字母出现错误

查看base64_table字符串,发现没有问题,说明源代码中有对字符串进行操作的部分

查看源代码,发现一个函数是 O_OLootAtYou

for ( i = 0; i <= 9; ++i ) {
    v0 = base64_table[i];
    base64_table[i] = base64_table[19 - i];
    result = 19 - i;
    base64_table[result] = v0;
}

主函数中说有一个奇怪的字符串,显然是用错误的加密得到的结果

对其进行更正后解密

import base64

str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
table = []
for c in str:
    table.append(c)
for i in range(10):
    c = table[i]
    table[i] = table[19 - i]
    table[19 - i] = c

crypto = "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD=="
msg = ""
for i in range(len(crypto)):
    if ord(crypto[i]) >= ord('A') and ord(crypto[i]) <= ord('Z'):
        for j in range(26):
            if table[j] == crypto[i]:
                msg += chr(j + ord('A'))
    else:
        msg += crypto[i]
print (msg)
print (base64.b64decode(msg))

运行得到flag为 wctf2020{Base64_is_the_start_of_reverse}

[WUSTCTF2020]level4

运行一下程序,发现这道题和结构体有关

拖入ida

看到主函数中输出的是type1和type2函数的结果

查看type1和type2

if ( a1 ) {
    type1(*((_QWORD *)a1 + 1));
    putchar(*a1);
    result = type1(*((_QWORD *)a1 + 2));
}
if ( a1 ) {
    type2(*((_QWORD *)a1 + 1));
    type2(*((_QWORD *)a1 + 2));
    result = putchar(*a1);
}

明显是二叉树的中序遍历和后序遍历

反推出前序遍历,得到flag: wctf2020{This_IS_A_7reE}

[WUSTCTF2020]funnyre

拖入ida后,通过start函数进入main

发现main函数未定义,按p创建函数后F5,查看代码

看到一串如下代码:

    v7 = 0LL;
    do
      *(_BYTE *)(v6 + v7++ + 5) ^= 0x91u;       // 每一项异或0x91
    while ( v7 != 32 );

除此之外,还有一些错误代码标红

以及JUMPOUT函数

例如:

    JUMPOUT(1, (char *)&loc_400B81 + 1);
    JUMPOUT(0, (char *)&loc_400B81 + 1);
    v89 = MEMORY[0xFFFFFFFF81003D16](v6);
    JUMPOUT(v91, (char *)&loc_400B88 + 2);
    *(_DWORD *)((unsigned int)(v89 - 1065138106) - 125LL) += v92;
    *(_BYTE *)(a3 - 15) &= BYTE1(v90);

经过学习后发现JUMPOUT函数是经典花指令,需要patch去除

.text:0000000000400607 loc_400607:                             ; CODE XREF: main+64↓j
.text:0000000000400607                 xor     byte ptr [rdx+rax+5], 91h
.text:000000000040060C                 add     rax, 1
.text:0000000000400610                 cmp     rax, 20h
.text:0000000000400614                 jnz     short loc_400607
.text:0000000000400616                 jz      short near ptr loc_40061A+1
.text:0000000000400618                 jnz     short near ptr loc_40061A+1
.text:000000000040061A
.text:000000000040061A loc_40061A:                             ; CODE XREF: main+66↑j
.text:000000000040061A                                         ; main+68↑j
.text:000000000040061A                 call    near ptr 0FFFFFFFF810037AFh
.text:000000000040061F                 jz      short near ptr loc_400621+2
.text:0000000000400621

发现40061A这个位置其实被跳过了

将这个位置的数据变为90(即nop)

按c将其转换回汇编代码

得到正确代码

.text:0000000000400616                 jz      short loc_40061B
.text:0000000000400618                 jnz     short loc_40061B
.text:000000000040061A                 nop
.text:000000000040061B
.text:000000000040061B loc_40061B:                             ; CODE XREF: main+66↑j
.text:000000000040061B                                         ; main+68↑j
.text:000000000040061B                 nop
.text:000000000040061C                 xor     eax, eax
.text:000000000040061E
.text:000000000040061E loc_40061E:                             ; CODE XREF: main+7B↓j
.text:000000000040061E                 xor     byte ptr [rdx+rax+5], 0CDh
.text:0000000000400623                 add     rax, 1
.text:0000000000400627                 cmp     rax, 20h
.text:000000000040062B                 jnz     short loc_40061E
.text:000000000040062D                 xor     eax, eax
.text:000000000040062F

在ida汇编模式中将后面几个标红的部分进行同样的修改,随后再F5即可查看到正确的代码

找到函数结尾

if ( memcmp(v5 + 5, &unk_4025C0, 0x20uLL) )
      JUMPOUT(&loc_4005BB);

发现unk字符串,将其进行如上函数的逆变换,即可得到最终结果

我选择的方法是复制到vscode然后利用快捷键进行快速复制,最终得到答案

代码如下:

res = [0xD9, 0x2C, 0x27, 0xD6, 0xD8, 0x2A, 0xDA, 0x2D, 0xD7, 0x2C, 0xDC, 0xE1, 0xDB, 0x2C, 0xD9, 0xDD, 0x27, 0x2D, 0x2A, 0xDC, 0xDB, 0x2C, 0xE1, 0x29, 0xDA, 0xDA, 0x2C, 0xDA, 0x2A, 0xD9, 0x29, 0x2A]
add = [80, 64, 79, 30, 91, 9, 5, 99, 87, 83, 59, 58, 1, 90, 57, 65, 53, 41, 85, 89, 44, 70, 12, 84, 10, 74, 17, 38, 43, 33, 11, 94, 86, 55, 32, 97, 68, 50, 67, 71, 96, 56, 6, 73, 52, 42, 61, 69, 14, 75, 4, 40, 37, 15, 77, 2, 23, 62, 29, 49, 47, 27, 66, 82, 46, 19, 88, 63, 39, 35, 25, 51, 18, 92, 95, 3, 72, 48, 36, 93, 76, 22, 98, 81, 13, 45, 34, 78, 26, 54, 28, 31, 20, 16, 7, 24, 60, 21, 8]
xor1 = [0xF9, 0xA9, 0x4E, 0xD3, 0xC7, 0xE2, 0xD2, 0x33, 0xA8, 0x96, 0xBD, 0xC, 0x13, 0x2F, 0x73, 0x65, 0x9C, 0x12, 2, 0x32, 0x10, 0x84, 0xED, 0x95, 0x4D, 0x75, 0x2C, 0x5D, 0x39, 0x18, 0x4C, 0x49, 0x37, 0xF0, 0x99, 0x41, 0x86, 0x76, 0xF5, 5, 0xC8, 0x64, 0xFA, 0x50, 0x3B, 8, 0xE9, 0x23, 0xC3, 0x68, 0x67, 0xff, 0x7D, 0x9D, 0x1D, 0xDA, 0xD8, 0xEB, 0xF6, 0xE3, 0x98, 0xE1, 0x34, 0x82, 0x7F, 0xD5, 0xE7, 0xB8, 0xDC, 0x97, 0xA3, 7, 0xB6, 0xB, 0x14, 0xCE, 0x66, 0x62, 0xEF]
# 中间有一个0xff,对应这取反的那个循环
for i in range(len(res)):
    for j in add:
        res[i] -= j
        res[i] %= 256

for i in range(len(res)):
    for j in xor1:
        res[i] ^= j

for i in range(len(res)):
    res[i] += 128
    res[i] %= 256

xor2 = [0x91, 0xCD, 0x6A, 0x59, 0xA, 0xF3, 0xCA, 0x3E, 0x6C, 0x4F, 0x24, 0x83, 0xC4, 0x53, 4, 0x9E, 0x42, 0xE, 0x8D, 0x38, 0x7A, 0xDD, 0x52, 0x1B, 0xAA, 0xAE, 0xF8, 0x58, 0xF2, 0x9F, 0x3C, 0xA1, 6, 0x78, 0x70, 0x28, 0xEA, 0x48, 0xE4, 0x6E, 0x40, 0x89, 0x16, 0xD7, 0xB5, 0xD, 0x17, 0x5A, 0xB1, 0x69, 0x5C, 0x21, 0xE5, 0x7E, 0x2A, 0x94, 0xBC, 1, 0x74, 0x57, 0x6D, 0x1E, 0xA2, 0x6B, 0x22, 0xC2, 0x3D, 0x44, 0x90, 0x8C, 0xB3, 0xA6, 0x79, 0x61, 0xD9, 0x5B, 0x1A, 0x43, 0x8F, 0xA5, 0xEE, 0x25, 0x46, 0xE6, 0x88, 0x20, 0x71, 0xE8, 9, 0x8A, 0x7B, 0xB4, 0x19, 0x15, 0x4A, 0x47, 0xDB, 0x72, 0x5F, 0x26, 0xA7, 0x8B, 0xBA, 0xBF, 0x87, 0x36, 0x3F, 0xFE, 0x77, 0x1C, 0x81, 0x11, 0x2E, 0x7C, 0x63, 0x45, 0xF4, 0x56, 0xF1, 0xB0, 0xD1, 0xE0, 0xF, 0x93, 0xD6, 0x1F, 0xCC, 0x4B, 0xCF, 0xDF, 0x55, 0xB9, 0x2B, 0x85, 0x31, 0x29, 0xFD, 0x3A, 0x5E, 0xDE, 3, 0xC6, 0xC1, 0xC5, 0x54, 0xBB, 0xFC, 0xBE, 0xEC, 0xC0, 0xAD, 0xA4, 0xD0, 0x35, 0xB7, 0x51, 0xAB, 0x2D, 0xAF, 0x92, 0x60, 0xAC, 0x30, 0xD4, 0xCB, 0x9B, 0x9A, 0xFB, 0x6F, 0xF7, 0x8E, 0xA0, 0x27, 0xC9]

for i in range(len(res)):
    for j in xor2:
        res[i] ^= j
    print(chr(res[i]), end='')
print ()

得到 flag: 1dc20f6e3d497d15cef47d9a66d6f1af

[GUETCTF2019]re

先upx脱壳

然后拖入ida

找到关键字符串

进入函数

发现是对每一位进行乘法然后判断

直接扔到python里算一下,发现 a[6] 缺失,以及有一位算不出来,可能出现了溢出

用c语言把这一位算出来,得到是 a

对第7位进行暴力破解,发现1可以,得到flag

x = [ 1629056,  6771600,  3682944, 10431000,  3977328,  5138336,  7532250,  5551632,  3409728, 13013670,  6088797,  7884663,  8944053,  5198490,  4544518,   10115280,3645600,  9667504,  5364450, 13464540,  5488432, 14479500,  6451830,  6252576,  7763364,  7327320,  8741520,  8871876,  4086720,  9374400, 5759124 ]
a = [ 166163712 , 731332800 , 357245568 , 1074393000, 489211344 , 518971936 , 406741500 , 294236496 , 177305856 ,  650683500,  298351053,  386348487,  438258597,  249527520,  445362764, 981182160, 174988800, 493042704,  257493600,  767478780,  312840624,  140451150,  316139670,  619005024,  372641472,  373693320,  498266640,  452465676,  208422720,  515592000,  719890500 ]
for i in range(len(x)):
    if (a[i] % x[i] == 0):
        print (chr(a[i] // x[i]), end='')
    else:
        print ('\n', i)
print ('\nfinish')

输出为

flag{e65421110ba03099
 21
1c039337}
finish

flag: flag{e165421110ba03099a1c039337}

再写个c把这一位算一下(其实不如直接用c暴力破解了)

#include <stdio.h>

int main(){
    char x;
    for (x = 0x21; x < 0x7f; x++){
        if (14479500 * x == 1404511500){
            printf("%c\n", x);
        }
    }
    printf("finish\n");
    return 0;
}

[GUETCTF2019]number game

先拖入ida进行静态分析,查看main函数核心代码

  __isoc99_scanf("%s", &v5, a3);
  if ( (unsigned int)check_input((const char *)&v5) )// 长度为10且只能是"01234"
  {
    v3 = sub_400758((__int64)&v5, 0, 10);
    sub_400807((__int64)v3, (__int64)&v7);
    v9 = 0;
    sub_400881((char *)&v7);                    // v7 = "0421421430"
    if ( (unsigned int)sub_400917() )
    {
      puts("TQL!");
      printf("flag{", &v7);
      printf("%s", &v5);
      puts("}");
    }
    else
    {
      puts("your are cxk!!");
    }
  }

输入的字符串需要通过两个if语句

先看第一个if语句对应的函数

  if ( strlen(a1) == 10 )
  {
    for ( i = 0; i <= 9; ++i )
    {
      if ( a1[i] > 52 || a1[i] <= 47 )
      {
        puts("Wrong!");
        return 0LL;
      }
    }
    result = 1LL;
  }
  else
  {
    puts("Wrong!");
    result = 0LL;
  }

这是对输入数据的简单要求,要求是10个0-4自成的字符串

再查看第二个if中的函数

  v1 = 1
  for ( i = 0; i <= 4; ++i )
  {
    for ( j = 0; j <= 4; ++j )
    {
      for ( k = j + 1; k <= 4; ++k )
      {
        if ( *((_BYTE *)&unk_601060 + 5 * i + j) == *((_BYTE *)&unk_601060 + 5 * i + k) )
          v1 = 0;
        if ( *((_BYTE *)&unk_601060 + 5 * j + i) == *((_BYTE *)&unk_601060 + 5 * k + i) )
          v1 = 0;
      }
    }
  }

要求在某条件下unk_601060中的两个字符不相同

再查看一下中间的几个函数

先查看 sub_400881 函数

__int64 __fastcall sub_400881(char *a1)
{
  __int64 result; // rax

  byte_601062 = *a1;
  byte_601067 = a1[1];
  byte_601069 = a1[2];
  byte_60106B = a1[3];
  byte_60106E = a1[4];
  byte_60106F = a1[5];
  byte_601071 = a1[6];
  byte_601072 = a1[7];
  byte_601076 = a1[8];
  result = (unsigned __int8)a1[9];
  byte_601077 = a1[9];
  return result;
}

是将a1中的数据放到指定的内存位置上

查看这个内存地址

data:0000000000601060 unk_601060      db  31h ; 1
.data:0000000000601061                 db  34h ; 4
.data:0000000000601062 byte_601062     db 23h                  ; DATA XREF: sub_400881+F↑w
.data:0000000000601063                 db  32h ; 2
.data:0000000000601064                 db  33h ; 3
.data:0000000000601065                 db  33h ; 3
.data:0000000000601066                 db  30h ; 0
.data:0000000000601067 byte_601067     db 23h                  ; DATA XREF: sub_400881+1D↑w
.data:0000000000601068                 db  31h ; 1
.data:0000000000601069 byte_601069     db 23h                  ; DATA XREF: sub_400881+2B↑w
.data:000000000060106A                 db  30h ; 0
.data:000000000060106B byte_60106B     db 23h                  ; DATA XREF: sub_400881+39↑w
.data:000000000060106C                 db  32h ; 2
.data:000000000060106D                 db  33h ; 3
.data:000000000060106E byte_60106E     db 23h                  ; DATA XREF: sub_400881+47↑w
.data:000000000060106F byte_60106F     db 23h                  ; DATA XREF: sub_400881+55↑w
.data:0000000000601070                 db  33h ; 3
.data:0000000000601071 byte_601071     db 23h                  ; DATA XREF: sub_400881+63↑w
.data:0000000000601072 byte_601072     db 23h                  ; DATA XREF: sub_400881+71↑w
.data:0000000000601073                 db  30h ; 0
.data:0000000000601074                 db  34h ; 4
.data:0000000000601075                 db  32h ; 2
.data:0000000000601076 byte_601076     db 23h                  ; DATA XREF: sub_400881+7F↑w
.data:0000000000601077 byte_601077     db 23h                  ; DATA XREF: sub_400881+8D↑w
.data:0000000000601078                 db  31h ; 1

发现与 unk_601060 对应

说明这个程序是将输入数据填到指定内存上,然后判断是否符合某个特殊条件

由于总共有25个字符,并且程序中出现了形如 5 * i + j 的值,以及题目名称 number_game 推测是一种类似数独的填数游戏,每行每列数字只能为01234且不能重复

查看已有的数字

14 23
30 1 
0 23 
 3  0
42  1

那么最后v7中应该为 0421421430

简单查看了 sub_400758sub_400807 后,确定这两个函数只进行了顺序的转换,只要得知其结果就可以

因此使用gdb进行动态调试,目的是找到这两个函数的顺序调换结果

首先 start 运行程序

gdb-peda$ start

0x40a62 处打上断点(输入后,第一个if前)

gdb-peda$ b *0x400a62

运行,并输入 0123456789

接下来需要绕过第一个if判断

在调用函数前修改eip,直接跳到jz后面

gdb-peda$ set var $rip=0x400a76

接下来需要查看两个函数运行后的值,这个值是 sub_400881 的参数,因此在call这个函数前打上断点,并查看 rdi 寄存器的结果即可

gdb-peda$ b *0x400aae
Breakpoint 3 at 0x400aae
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffedf40
RBX: 0x400b20 --> 0x41ff894156415741
RCX: 0xe
RDX: 0x7ffffffedf40
RSI: 0x7ffffffedf40
RDI: 0x7ffffffedf40
RBP: 0x7ffffffedf60
RSP: 0x7ffffffedf20
RIP: 0x400aae --> 0xb8fffffdcee8
R8 : 0x6033d0 --> 0x36 ('6')
R9 : 0x7c ('|')
R10: 0x4003ce --> 0x5f00636f6c6c616d ('malloc')
R11: 0x7fffff78bbe0 --> 0x6033e0 --> 0x0
R12: 0x4005e0 --> 0x89485ed18949ed31
R13: 0x7ffffffee050
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400aa3:    mov    BYTE PTR [rbp-0x16],0x0
   0x400aa7:    lea    rax,[rbp-0x20]
   0x400aab:    mov    rdi,rax
=> 0x400aae:    call   0x400881
   0x400ab3:    mov    eax,0x0
   0x400ab8:    call   0x400917
   0x400abd:    test   eax,eax
   0x400abf:    je     0x400afc
Guessed arguments:
arg[0]: 0x7ffffffedf40
[------------------------------------stack-------------------------------------]
Invalid $SP address: 0x7ffffffedf20
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x0000000000400aae in ?? ()

注意到 RDI: 0x7ffffffedf40

因此查看这个内存地址下的值即可

gdb-peda$ x/2x 0x7ffffffedf40
0x7ffffffedf40: 0x3530343931383337      0x0000000000003632

根据小端模式

0123456789 被转换为 7381940526

根据这个信息将 0421421430 还原,得到 1134240024

运行程序

$ ./number_game
1134240024
TQL!
flag{1134240024}

[GUETCTF2019]encrypt

拖入ida,查看主函数

  printf("please input your flag:", a2, v19);
  scanf("%s", &s);
  memset(&v9, 0, 0x408uLL);
  sub_4006B6(&v9, (__int64)&v10, 8);
  v3 = strlen(&s);
  sub_4007DB(&v9, (__int64)&s, v3);
  v4 = strlen(&s);
  sub_4008FA((__int64)&s, v4, v19, &v6);
  for ( i = 0; i <= 50; ++i )
  {
    if ( v19[i] != byte_602080[i] )
    {
      puts("Wrong");
      return 0LL;
    }
  }
  puts("Good");

在进行判断前执行了三个函数

最后一个函数是一个类base64

  while ( v20 < a2 )
  {
    v4 = v20++;
    v17 = *(_BYTE *)(v4 + a1);
    if ( v20 >= a2 )
    {
      v6 = 0;
    }
    else
    {
      v5 = v20++;
      v6 = *(_BYTE *)(v5 + a1);
    }
    v18 = v6;
    if ( v20 >= a2 )
    {
      v8 = 0;
    }
    else
    {
      v7 = v20++;
      v8 = *(_BYTE *)(v7 + a1);
    }
    v9 = v8;
    v10 = v19;
    v11 = v19 + 1;
    a3[v10] = ((v17 >> 2) & 0x3F) + 61;
    v12 = v11++;
    a3[v12] = ((((v18 & 0xFF) >> 4) | 16 * v17) & 0x3F) + 61;
    a3[v11] = ((((v9 & 0xFF) >> 6) | 4 * v18) & 0x3F) + 61;
    v13 = v11 + 1;
    v19 = v11 + 2;
    a3[v13] = (v9 & 0x3F) + 61;
  }
  if ( a2 % 3 == 1 )
  {
    a3[--v19] = 61;
  }
  else if ( a2 % 3 != 2 )
  {
    goto LABEL_15;
  }
  a3[v19 - 1] = 61;

将每一位减少61后映射到base64的字符串上就可以得到base64结果

str = 'Z`TzzTrD|fQP[_VVL|yneURyUmFklVJgLasJroZpHRxIUlH\\vZE' # 注意转义字符
base = ''
map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for c in str:
    base += map[ord(c) - 61]
print (base)
# base = 'djX99X1H/pUTeiZZP/8xoYV8YwJuvZNqPk2N1ydzLV7MYvLf5dI'
# 最后再补充一个 '=' 即可

破解完这个函数后再查看上两个函数

根据调用判断第一个函数为第二个函数提供密钥,这个应该是固定结果,动态调试即可得到

查看第二个函数

  for ( i = 0; i < a3; ++i ) {
    v7 = (unsigned __int8)(v7 + 1);
    v3 = *(_DWORD *)(4LL * v7 + v9);
    v8 = (unsigned __int8)(v8 + v3);
    v4 = *(_DWORD *)(4LL * v8 + v9);
    *(_DWORD *)(v9 + 4LL * v7) = v4;
    *(_DWORD *)(v9 + 4LL * v8) = v3;
    *(_BYTE *)(i + a2) ^= *(_BYTE *)(4LL * (unsigned __int8)(v3 + v4) + v9);
  }

这对输入的每一位做了个异或运算,异或的数字也是固定的,直接进行动态调试即可(根据经验猜测是RC4或者与其类似)

根据base64的位数得知flag最多有39位

进行动态调试

gdb-peda$ start
gdb-peda$ c
Continuing.
please input your flag:flag{abcdefghijklmnopqrstuvwxyz0123456}
[----------------------------------registers-----------------------------------]
RAX: 0x27 ("'")
RBX: 0x0
RCX: 0x0
RDX: 0x7ffffffed9c0 ("flag{abcdefghijklmnopqrstuvwxyz0123456}")
RSI: 0x7ffffffed9b0 --> 0x4010202030302010
RDI: 0x7ffffffed9e0 --> 0x7d363534333231 ('123456}')
RBP: 0x7ffffffeded0 --> 0x400c80 --> 0x41ff894156415741
RSP: 0x7ffffffed590 --> 0x0
RIP: 0x400bbc --> 0xfffaf08d8d48c289
R8 : 0x0
R9 : 0x0
R10: 0x3
R11: 0x7fffff18ee90 (<__memset_avx2_unaligned_erms>:    vmovd  xmm0,esi)
R12: 0x4005c0 --> 0x89485ed18949ed31
R13: 0x7ffffffedfb0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400bad:    lea    rax,[rbp-0x510]
   0x400bb4:    mov    rdi,rax
   0x400bb7:    call   0x400550 <strlen@plt>
=> 0x400bbc:    mov    edx,eax
   0x400bbe:    lea    rcx,[rbp-0x510]
   0x400bc5:    lea    rax,[rbp-0x930]
   0x400bcc:    mov    rsi,rcx
   0x400bcf:    mov    rdi,rax
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed590 --> 0x0
0008| 0x7ffffffed598 --> 0x5f4f636d ('mcO_')
0016| 0x7ffffffed5a0 --> 0x0
0024| 0x7ffffffed5a8 --> 0x31000000b0
0032| 0x7ffffffed5b0 --> 0x7000000075 ('u')
0040| 0x7ffffffed5b8 --> 0xdf000000f8
0048| 0x7ffffffed5c0 --> 0x3c00000007
0056| 0x7ffffffed5c8 --> 0x7100000078 ('x')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0000000000400bbc in ?? ()
gdb-peda$ x/10x $rdx
0x7ffffffed9c0: 0x6362617b67616c66      0x6b6a696867666564
0x7ffffffed9d0: 0x737271706f6e6d6c      0x307a797877767574
0x7ffffffed9e0: 0x007d363534333231      0x0000000000000000
0x7ffffffed9f0: 0x0000000000000000      0x0000000000000000
0x7ffffffeda00: 0x0000000000000000      0x0000000000000000
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffed5a0 --> 0x1c00000027
RBX: 0x0
RCX: 0x138
RDX: 0x1c
RSI: 0x7d ('}')
RDI: 0x7ffffffed5a0 --> 0x1c00000027
RBP: 0x7ffffffeded0 --> 0x400c80 --> 0x41ff894156415741
RSP: 0x7ffffffed590 --> 0x0
RIP: 0x400bd7 --> 0x48fffffaf0858d48
R8 : 0x0
R9 : 0x0
R10: 0x3
R11: 0x7fffff18ee90 (<__memset_avx2_unaligned_erms>:    vmovd  xmm0,esi)
R12: 0x4005c0 --> 0x89485ed18949ed31
R13: 0x7ffffffedfb0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400bcc:    mov    rsi,rcx
   0x400bcf:    mov    rdi,rax
   0x400bd2:    call   0x4007db
=> 0x400bd7:    lea    rax,[rbp-0x510]
   0x400bde:    mov    rdi,rax
   0x400be1:    call   0x400550 <strlen@plt>
   0x400be6:    mov    esi,eax
   0x400be8:    lea    rcx,[rbp-0x93c]
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed590 --> 0x0
0008| 0x7ffffffed598 --> 0x5f4f636d ('mcO_')
0016| 0x7ffffffed5a0 --> 0x1c00000027
0024| 0x7ffffffed5a8 --> 0x67000000b0
0032| 0x7ffffffed5b0 --> 0x4b000000b8
0040| 0x7ffffffed5b8 --> 0xe000000069
0048| 0x7ffffffed5c0 --> 0x3f000000b9
0056| 0x7ffffffed5c8 --> 0xc5000000ec
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x0000000000400bd7 in ?? ()
gdb-peda$ x/10x 0x7ffffffed9c0
0x7ffffffed9c0: 0xc6ad437df5fd3576      0xa862a26e0d237b16
0x7ffffffed9d0: 0x2c84ae7c0c342488      0x5e656c6895cb5d7c
0x7ffffffed9e0: 0x00e699b5d8f9689b      0x0000000000000000
0x7ffffffed9f0: 0x0000000000000000      0x0000000000000000
0x7ffffffeda00: 0x0000000000000000      0x0000000000000000

将输入在函数执行前后两次的结果进行异或即可得到异或的数字,再与base64解码的结果进行异或即可,注意大小端问题

from libnum import n2s, s2n

before = 0x7d363534333231307a797877767574737271706f6e6d6c6b6a6968676665646362617b67616c66
after = 0xe699b5d8f9689b5e656c6895cb5d7c2c84ae7c0c342488a862a26e0d237b16c6ad437df5fd3576
flag = b"v5\xfd\xf5}G\xfe\x95\x13z&Y?\xff1\xa1\x85|c\x02n\xbd\x93j>M\x8d\xd7's-^\xccb\xf2\xdf\xe5\xd2"[::-1]

print (len(flag))

print (n2s(s2n(flag) ^ before ^ after)[::-1])
# b'flag{e10adc3949ba59abbe56e057f20f883e}\x9b'

得到flag

flag{e10adc3949ba59abbe56e057f20f883e}

[MRCTF2020]Transform

dword = [ 9, 0xa, 0xf, 0x17, 0x7, 0x18, 0xc, 0x6, 0x1, 0x10, 0x3, 0x11, 0x20, 0x1D, 0x0b, 0x1e, 0x1b, 0x16, 0x4, 0x0d, 0x13, 0x14, 0x15, 0x2, 0x19, 0x5, 0x1f, 0x8, 0x12, 0x1a, 0x1c, 0xe, 0 ]

flag = [ 0x67, 0x79, 0x7b, 0x7f, 0x75, 0x2b, 0x3c, 0x52, 0x53, 0x79, 0x57, 0x5E, 0x5D, 0x42, 0x7B, 0x2D, 0x2A, 0x66, 0x42, 0x7E, 0x4C, 0x57, 0x79, 0x41, 0x6B, 0x7E, 0x65, 0x3C, 0x5C, 0x45, 0x6F, 0x62, 0x4D, 0x3f]
print(len(dword), len(flag))

for i in range(len(dword)):
    flag[i] ^= dword[i]

code = [ 0 for i in range(33)]
for i in range(len(dword)):
    code[dword[i]] = flag[i]
 
for i in range(len(code)):
    print(chr(code[i]), end = '')
print()

flag: MRCTF{Tr4nsp0sltiON_Clph3r_1s_3z}

[MRCTF2020]Xor

flag = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g'

for i in range(27):
    print(chr(ord(flag[i]) ^ i), end='')

print()

flag: MRCTF{@_R3@1ly_E2_R3verse!}

[MRCTF2020]hello_world_go

.rodata:00000000004D3C58 unk_4D3C58      db  66h ; f             ; DATA XREF: main_main:loc_49A40A↑o
.rodata:00000000004D3C58                                         ; main_main+25C↑o
.rodata:00000000004D3C59                 db  6Ch ; l
.rodata:00000000004D3C5A                 db  61h ; a
.rodata:00000000004D3C5B                 db  67h ; g
.rodata:00000000004D3C5C                 db  7Bh ; {
.rodata:00000000004D3C5D                 db  68h ; h
.rodata:00000000004D3C5E                 db  65h ; e
.rodata:00000000004D3C5F                 db  6Ch ; l
.rodata:00000000004D3C60                 db  6Ch ; l
.rodata:00000000004D3C61                 db  6Fh ; o
.rodata:00000000004D3C62                 db  5Fh ; _
.rodata:00000000004D3C63                 db  77h ; w
.rodata:00000000004D3C64                 db  6Fh ; o
.rodata:00000000004D3C65                 db  72h ; r
.rodata:00000000004D3C66                 db  6Ch ; l
.rodata:00000000004D3C67                 db  64h ; d
.rodata:00000000004D3C68                 db  5Fh ; _
.rodata:00000000004D3C69                 db  67h ; g
.rodata:00000000004D3C6A                 db  6Fh ; o
.rodata:00000000004D3C6B                 db  67h ; g
.rodata:00000000004D3C6C                 db  6Fh ; o
.rodata:00000000004D3C6D                 db  67h ; g
.rodata:00000000004D3C6E                 db  6Fh ; o
.rodata:00000000004D3C6F                 db  7Dh ; }

[MRCTF2020]PixelShooter

先使用NoxPlayer打开这个apk,发现游戏结束后有个flag信息,说得分不够高,说明flag会在游戏结束时根据得分获得

使用apktool解压

$ java -jar apktool_2.5.0.jar d PixelShooter.apk

这个显然是unity3d逆向,找到 /assets/bin/Data/Managed,使用 dnSpy打开 Assembly-CSharp.dll

看到有个gameController,其中有个gameOver

    public void GameOver()
    {
        this.isGameOver = true;
        this.UI.GetComponent<UIController>().GameOver(this.score, this.bestScore);
        if (PlayerPrefs.HasKey("bestScore"))
        {
            this.bestScore = Mathf.Max(this.score, PlayerPrefs.GetInt("bestScore"));
        }
        else
        {
            this.bestScore = this.score;
        }
        base.GetComponent<AudioSource>().Stop();
    }

有个获取ui的函数,查看

    public void GameOver(int score, int bestScore)
    {
        this.pad.SetActive(false);
        Time.timeScale = 0f;
        string text = "您的飞机已坠毁\n";
        if (bestScore < score)
        {
            string text2 = text;
            text = string.Concat(new object[]
            {
                text2,
                "获得最高分:",
                score,
                "!\n"
            });
            PlayerPrefs.SetInt("bestScore", score);
        }
        if (score < 20)
        {
            text += "少年继续努力!要拿到flag还差亿点点\n";
        }
        else if (score < 100)
        {
            text += "战绩不错!但是要拿到flag还差亿点";
        }
        else if (score < 500)
        {
            text += "惊人的成绩!!但是要拿到flag还差一点\n";
        }
        else
        {
            text += "MRCTF{Unity_1S_Fun_233}\n";
        }
        if (Time.time - this.lastTime < 15f)
        {
            text += "以及,别作死啊!\n";
        }
        else if (Time.time - this.lastTime < 60f)
        {
            text += "以及注意闪避!";
        }
        this.gameOverText.text = text;
        this.gameOverUI.SetActive(true);
    }

拿到flag

Built with Hugo
Theme Stack designed by Jimmy