Back

NepCTF2021 re writeups

NepCTF2021 reverse 前四题,不过比赛哪儿有吃瓜好玩,属于是315打假晚会了

hardsharp (.NET 逆向)

使用exeinfo发现是 C# .NET文件

直接用 dnSpy 打开

找到主函数

private static void Main(string[] args)
{
	AesClass aesClass = new AesClass();
	string text = "";
	string strB = "1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0";
	byte[] array = new byte[]
	{
		81,
		82,
		87,
		81,
		82,
		87,
		68,
		92,
		94,
		86,
		93,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18,
		18
	};
	Console.WriteLine("Welcome to nepnep csharp test! plz input the magical code:");
	string text2 = Console.ReadLine();
	if (text2.Length != 37)
	{
		Console.WriteLine("Nope!");
		Console.ReadKey();
		return;
	}
	if (text2.Substring(0, 4) != "Nep{" || text2[36] != '}')
	{
		Console.WriteLine("Nope!");
		Console.ReadKey();
		return;
	}
	for (int i = 0; i < 32; i++)
	{
		text += Convert.ToChar((int)(array[i] ^ 51)).ToString();
	}
	if (string.Compare(aesClass.AesEncrypt(text2, text), strB) == 0)
	{
		Console.WriteLine("wow, you pass it!");
		Console.ReadKey();
		return;
	}
	Console.WriteLine("Nope!");
	Console.ReadKey();
}

这才是真正的签到题吧…

发现是 AES 加密

进入 AesEncrypt 函数看一眼

public string AesEncrypt(string str, string key)
{
	if (string.IsNullOrEmpty(str))
	{
		return null;
	}
	byte[] bytes = Encoding.UTF8.GetBytes(str);
	byte[] array = new RijndaelManaged
	{
		Key = Encoding.UTF8.GetBytes(key),
		Mode = CipherMode.ECB,
		Padding = PaddingMode.PKCS7
	}.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);
	return Convert.ToBase64String(array, 0, array.Length);
}

发现使用了 ECB 模式,那么直接上网找个 python 脚本计算就行

array = [81, 82, 87, 81, 82, 87, 68, 92, 94, 86, 93, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18]
key = ''
for i in array:
    key += chr(i ^ 51)
print (f'key: {key}')
print (f'key length: {len(key)}')

import base64
from Crypto.Cipher import AES


message = '1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0'
encrypt_data = message

cipher = AES.new(key)
result2 = base64.b64decode(encrypt_data)
a = cipher.decrypt(result2)

a = a.decode('utf-8','ignore')
a = a.rstrip('\n')
a = a.rstrip('\t')
a = a.rstrip('\r')
a = a.replace('\x06','')
print('\n','data:',a)

得到结果:

key: badbadwomen!!!!!!!!!!!!!!!!!!!!!
key length: 32

 data: Nep{up_up_down_down_B_a_b_A_Nep_nep~}

二十六进制 (c / c++ 逆向)

先用exeinfo打开,发现是无壳32位c++代码。

用ida打开,找到有用的字符串,定位关键代码

void __noreturn sub_4010A0()
{
  __int64 v0; // rax
  char Dst; // [esp+0h] [ebp-108h]

  memset(&Dst, 0, 0xFFu);
  dword_403378 = (int)malloc(8u);
  Memory = (void *)dword_403378;
  *(_DWORD *)(dword_403378 + 4) = 0;
  sub_401020("plz input right num:\n", Dst);
  sub_401060("%s", &Dst, 32);
  v0 = atoi64(&Dst);
  sub_401120(v0);
}

输入一个数字,将其传入 sub_401120 函数。

进入这个函数

void __cdecl __noreturn sub_401120(__int64 a1)
{
  signed __int64 v1; // rdi
  int v2; // eax
  int v3; // edx
  char v4; // cl

  v1 = __PAIR__(a1, HIDWORD(a1));
  if ( a1 )
  {
    do
    {
      v2 = sub_401F00(__PAIR__(v1, HIDWORD(v1)), 0x1Au, 0);
      LODWORD(v1) = v3;
      v4 = byte_402194[HIDWORD(v1) - 26 * v2];  // 2163qwe)(*&^%489$!057@#><A
      HIDWORD(v1) = v2;
      sub_401160(v4 ^ 7);
    }
    while ( v1 );
  }
  sub_401190();
}

根据题目信息的提示,这是把输入数字转换成为26进制,随后进行一个异或运算,然后存到一个地址里。

进入 sub_401190 函数

void __noreturn sub_401190()
{
  _DWORD *v0; // esi
  int v1; // ecx
  _DWORD *v2; // ebx
  unsigned int v3; // edi
  unsigned int v4; // esi
  char *v5; // ecx
  char v6; // dl
  int v7; // eax
  int v8; // edi
  void *v9; // eax
  int v10; // [esp+10h] [ebp-4h]

  v0 = Memory;
  v1 = 0;
  v10 = 0;
  v2 = Memory;
  v3 = strlen(aFb726);
  if ( v3 )
  {
    v4 = 0;
    do
    {
      if ( !v2 )
        break;
      v5 = &aFb726[v4];
      v6 = *(_BYTE *)v2;
      v7 = v10 + 1;
      v2 = (_DWORD *)v2[1];
      ++v4;
      if ( v6 != *v5 )
        v7 = v10;
      v1 = v7;
      v10 = v7; // 判断某内存种对应位置是否与 aFb726 字符串相同,相同就在长度上加 1
    }
    while ( v4 < v3 );
    v0 = Memory;
  }
  if ( v0 ) 
  {
    v8 = v1;
    do
    {
      v9 = (void *)v0[1];
      dword_403378 = (int)v0;
      Memory = v9;
      free(v0);
      v0 = Memory;
      --v8;
    }
    while ( Memory ); // 释放内存
  }
  else
  {
    v8 = -1;
  }
  if ( v10 != strlen(aFb726) ) // 根据之间计算的相同字母数量,判断两个字符串是否相同
  {
    puts("flag is Error!!!");
    exit(v8);
  }
  puts("flag is Right!!!, please md5('Nep{you_input_num}') submit th4 flag");
  system("pause");
  exit(v8);
}

所以只需要将 aFb726 字符串进行反向计算即可

>>> flag = 'Fb72>&6'
>>> str = '2163qwe)(*&^%489$!057@#><A'
>>> num = []
>>> for c in flag:
...     num.append(str.index(chr(ord(c) ^ 7)))
...
>>> num
[25, 6, 18, 19, 15, 17, 1]
>>> sum = 0
>>> for i in num[::-1]:
...     sum *= 26
...     sum += i
...
>>> sum
518100101

运行程序进行测试,得到

plz input right num:
518100101
flag is Right!!!, please md5('Nep{you_input_num}') submit th4 flag
请按任意键继续. . .

根据题目信息,计算一下数字的32位小写md5(一开始算的是 Nep{51…01} 这个字符串的md5,发现不对,所以算数字的试了一下)

得到flag

Nep{967fa25cbea166ded43127f141cff31a}

password (安卓逆向)

这道题做了好久都没做出来,结果发现是把base64的 ‘+/’ 记反了…


首先用 jeb 打开,找到 com.nepnep.app 中的 MainActivity

public class MainActivity extends AppCompatActivity {
    public Encrypt en;

    static {
        System.loadLibrary("native-lib");
    }

    public MainActivity() {
        super();
        this.en = new Encrypt();
    }

    protected void onCreate(Bundle arg4) {
        super.onCreate(arg4);
        this.setContentView(0x7F0B001C);  // activity_main
        this.findViewById(0x7F080057).setOnClickListener(new View$OnClickListener(this.findViewById(0x7F0800B7), this.findViewById(0x7F0800F2)) {  // btn, key, password
            public void onClick(View arg4) {
                if(MainActivity.this.verify(this.val$key.getText().toString()) == 0) {
                    System.out.println(this.val$key.getText().toString());
                    Toast.makeText(MainActivity.this, "key错误!", 0).show();
                }
                else if(MainActivity.this.en.file(this.val$passwd.getText().toString().getBytes(), this.val$key.getText().toString())) {
                    Toast.makeText(MainActivity.this, "明文正确,快去解压缩包获取flag吧!", 0).show();
                }
                else {
                    Toast.makeText(MainActivity.this, "明文错误!", 0).show();
                }
            }
        });
    }

    public native int verify(String arg1) {
    }
}

这段代码是读入 keypassword,然后先判断 key 是否正确,如果正确的话判断 password 是否正确

那么肯定先破解 key,发现 verify 是外部函数

找到 native-lib.so 文件,拖入ida,找到函数名里搜索 javaverify 之类的,找到这个判断函数

  v4 = (char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
  v5 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1344LL))(a1, v3);
  memset(&s, 0, 0x3E8uLL);
  sub_710(v4, (__int64)&s, v5 - 3 * (((unsigned __int64)('UUUV' * v5) >> 63) + ((unsigned __int64)('UUUV' * v5) >> 32)));
  sub_820(&s, (__int64)&v22);                   // s='th1s_1s_k3ya!!!!'
  if ( v5 <= 0 )
    goto LABEL_10;
  v6 = 0;
  while ( 1 )                                   // base64
  {
    v11 = v6;
    if ( v5 >= 3 )
    {
      *((_BYTE *)&v15 + v6) = aAbcdefghijklmn[*((char *)&v22 + v6)];// abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ
      *((_BYTE *)&v15 + v6 + 1) = aAbcdefghijklmn[*((char *)&v22 + v6 + 1)];
      *((_BYTE *)&v15 + v6 + 2) = aAbcdefghijklmn[*((char *)&v22 + v6 + 2)];
      v7 = aAbcdefghijklmn[*((char *)&v22 + v6 + 3)];
      v6 += 4;
      *((_BYTE *)&v15 + v11 + 3) = v7;
      goto LABEL_4;
    }
    if ( v5 == 1 )
      break;
    if ( v5 == 2 )
    {
      *((_BYTE *)&v15 + v6) = aAbcdefghijklmn[*((char *)&v22 + v6)];
      *((_BYTE *)&v15 + v6 + 1) = aAbcdefghijklmn[*((char *)&v22 + v6 + 1)];
      *((_BYTE *)&v15 + v6 + 2) = aAbcdefghijklmn[*((char *)&v22 + v6 + 2)];
      *((_BYTE *)&v15 + v6 + 3) = '=';
      goto LABEL_10;
    }
LABEL_4:
    v10 = __OFADD__(-3, v5);
    v8 = v5 == 3;
    v9 = v5 - 3 < 0;
    v5 -= 3;
    if ( (unsigned __int8)(v9 ^ v10) | v8 )
      goto LABEL_10;
  }
  *((_BYTE *)&v15 + v6) = aAbcdefghijklmn[*((char *)&v22 + v6)];
  *((_BYTE *)&v15 + v6 + 1) = aAbcdefghijklmn[*((char *)&v22 + v6 + 1)];
  *(_WORD *)((char *)&v15 + v6 + 2) = '==';
LABEL_10:
  (*(void (__fastcall **)(__int64, __int64, char *))(*(_QWORD *)a1 + 1360LL))(a1, v3, v4);
  v12 = _mm_movemask_epi8(
          _mm_and_si128(
            _mm_cmpeq_epi8(_mm_load_si128((const __m128i *)&v15), (__m128i)xmmword_BE0),
            _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)((char *)&v15 + 9)), (__m128i)xmmword_BD0)));// 3g6L2PWL2PXFmR+7ise7iq==
  __android_log_print(4LL, "nepnep", "%s", &v15);
  result = 0LL;
  if ( v12 == 0xFFFF )
  {
    __android_log_print(4LL, "nepnep", "key is true!", v13);
    result = 1LL;
  }
  return result;

我们的目的肯定是让 v12 = 0FFFF

虽然对于 mm 之类的指令不太清楚,但大概的意思应该还是两个字符串相等的,那么就让 (const __m128i *)&v15), (__m128i)xmmword_BE0)((char *)&v15 + 9)), (__m128i)xmmword_BD0)) 两两相等即可

在字符串中找到这两个,就能得到 3g6L2PWL2PXFmR+7ise7iq==,这个看起来是一个 base64,但解出来是乱码,所以还是得往前看。上面一大段从 while 开始的代码,显然就是 base64 的最后一步,点开字符串,发现这个不是标准的 base64。那么根据这个字符串反向求解 base64 就可以了。

实际操作时,继续阅读了 sub_820sub_710 两个函数。只看 sub_820 的话看不太懂,建议先看 sub_710,发现 sub_710 中,将一个字符串的每个字符根据 ascii 数值直接拆开到了八个 char 上,每个 char 存 '0''1'0x300x31 )。到了 sub_820 函数,则每六个进行合并,每一位运算看起来很奇怪,但如果用 0x300x31 带入会发现就是很简单的将 0 和1 重新组合起来。就是做了一个 base64 运算。

于是写个代码反向求解就可以了

import string
from libnum import s2n
import base64

en_key = '7+RmFXP2LWP2L6g3'[::-1] + '==qi7esi'[::-1]
true_base = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
fake_base = string.ascii_lowercase + string.digits + '+/' + string.ascii_uppercase
key = ''
for c in en_key:
    if c == '=':
        key += c
        continue
    key += true_base[fake_base.index(c)]

key = base64.b64decode(key)
print(key)

得到 key: th1s_1s_k3y!!!!!

这里我因为打错了base的字符串,卡了整整一天(

接下来就可以去求明文密码了

继续阅读 java 代码

public class Encrypt {
    public Encrypt() {
        super();
    }

    public void en1(int[] arg6, String arg7, int arg8) {
        int v0 = 0x100;
        byte[] v1 = new byte[v0];
        byte[] v7 = arg7.getBytes();
        int v2 = 0;
        int v3;
        for(v3 = 0; v3 < v0; ++v3) {
            arg6[v3] = 0x100 - v3;  // 256-1降序
            v1[v3] = v7[v3 % arg8];  // 用密钥填满v1
        }

        int v7_1 = 0;
        while(v2 < v0) {
            v7_1 = (arg6[v2] + v7_1 + v1[v2]) % 0x100;
            arg8 = arg6[v2];
            arg6[v2] = arg6[v7_1];
            arg6[v7_1] = arg8;
            ++v2;
        }
    }

    public void en2(int[] arg7, byte[] arg8, int arg9) {
        int v0 = 0;
        int v1 = 0;
        int v2 = 0;
        while(v0 < arg9) {
            v1 = (v1 + 1) % 0x100;
            v2 = ((arg7[v1] & 0xFF) + v2) % 0x100;
            int v3 = arg7[v1];
            arg7[v1] = arg7[v2];
            arg7[v2] = v3;
            arg8[v0] = ((byte)(arg8[v0] ^ arg7[((arg7[v1] & 0xFF) + (arg7[v2] & 0xFF)) % 0x100]));
            ++v0;
        }
    }

    public boolean file(byte[] arg6, String arg7) {  // passwd, key = 'th1s_1s_k3ya!!!!'
        int[] v0 = new int[0x100];
        int v1 = 17;
        int[] v2 = new int[]{0x8B, 0xD2, 0xD9, 0x5D, 0x95, 0xFF, 0x7E, 0x5F, 0x29, 0x56, 0x12, 0xB9, 0xEF, 0xEC, 0x8B, 0xD0, 0x45};
        this.en1(v0, arg7, arg7.length());
        this.en2(v0, arg6, arg6.length);
        if(arg6.length != v1) {
            return 0;
        }

        int v7;
        for(v7 = 0; v7 < v1; ++v7) {
            if((arg6[v7] & 0xFF) != v2[v7]) {
                return 0;
            }
        }

        return 1;
    }
}

懂的话就会发现这个就是一个s盒从 0-255 变成 256-1RC4 密码

不懂的话直接把代码复制出来,把 file 函数改写成 main 函数,反向求解一下就行(因为仔细观察就可以发现 en2 只对明文做了个异或运算,且异或的对象和明文本身没有关系),这里当密码学的题来做其实就好了。

这是我复制后改写的 java 代码

public class Main {
    public static void en1(int[] arg6, String arg7, int arg8) {
        int v0 = 0x100;
        byte[] v1 = new byte[v0];
        byte[] v7 = arg7.getBytes();
        int v2 = 0;
        int v3;
        for(v3 = 0; v3 < v0; ++v3) {
            arg6[v3] = 0x100 - v3;  // 256-1降序
            v1[v3] = v7[v3 % arg8];  // 用密钥填满v1
        }

        int v7_1 = 0;
        while(v2 < v0) {
            v7_1 = (arg6[v2] + v7_1 + v1[v2]) % 0x100;
            arg8 = arg6[v2];
            arg6[v2] = arg6[v7_1];
            arg6[v7_1] = arg8;
            ++v2;
        }
    }

    public static void en2(int[] arg7, int[] arg8, int arg9) {
        int v0 = 0;
        int v1 = 0;
        int v2 = 0;
        while(v0 < arg9) {
            v1 = (v1 + 1) % 0x100;
            v2 = ((arg7[v1] & 0xFF) + v2) % 0x100;
            int v3 = arg7[v1];
            arg7[v1] = arg7[v2];
            arg7[v2] = v3;
            arg8[v0] = ((byte)(arg8[v0] ^ arg7[((arg7[v1] & 0xFF) + (arg7[v2] & 0xFF)) % 0x100]));
            ++v0;
        }
    }
    public static void main(String[] args) {
        String arg7 = "th1s_1s_k3y!!!!!";
        int[] arg6 = new int[]{0x8B, 0xD2, 0xD9, 0x5D, 0x95, 0xFF, 0x7E, 0x5F, 0x29, 0x56, 0x12, 0xB9, 0xEF, 0xEC, 0x8B, 0xD0, 0x45};
        int[] v0 = new int[0x100];
        int v1 = 17;
        int[] v2 = new int[]{};
        en1(v0, arg7, arg7.length());
        en2(v0, arg6, arg6.length);
        for (int i = 0; i < arg6.length; i++) {
            System.out.print((char)(arg6[i] & 0xff));
        }
    }
}

当然也可以跟在第一段 python 后面继续写

import string
from libnum import s2n
import base64

en_key = '7+RmFXP2LWP2L6g3'[::-1] + '==qi7esi'[::-1]
true_base = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
fake_base = string.ascii_lowercase + string.digits + '+/' + string.ascii_uppercase
key = ''
for c in en_key:
    if c == '=':
        key += c
        continue
    key += true_base[fake_base.index(c)]

key = base64.b64decode(key)
print(key)
# th1s_1s_k3y!!!!!

ciphertext = [0x8B, 0xD2, 0xD9, 0x5D, 0x95, 0xFF, 0x7E, 0x5F, 0x29, 0x56, 0x12, 0xB9, 0xEF, 0xEC, 0x8B, 0xD0, 0x45]

def en1(key, len_key):
    res = []
    v0 = 0x100
    v1 = [0] * v0
    v7 = [c for c in key]
    v2 = 0
    for v3 in range(v0):
        res.append(0x100 - v3)
        v1[v3] = v7[v3 % len_key]
    v7_1 = 0
    while v2 < v0:
        v7_1 = (res[v2] + v7_1 + v1[v2]) % 0x100
        len_key = res[v2]
        res[v2] = res[v7_1]
        res[v7_1] = len_key
        v2 += 1
    return res

def en2(res_key, text, len_text):
    v0 = 0
    v1 = 0
    v2 = 0
    # text = bytes(text)
    while v0 < len_text:
        v1 = (v1 + 1) % 0x100
        v2 = ((res_key[v1] & 0xFF) + v2) % 0x100
        v3 = res_key[v1]
        res_key[v1] = res_key[v2]
        res_key[v2] = v3
        text[v0] = ((text[v0] ^ res_key[((res_key[v1] & 0xFF) + (res_key[v2] & 0xFF)) % 0x100]))
        v0 += 1
    return text

list_key = en1(key[:], len(key))
plaintext = en2(list_key[:], ciphertext[:], len(ciphertext))
print (plaintext)

flag = ''
for c in plaintext:
    flag += chr(c)

print (flag)

这样就拿到密码了 Y0uG3tTheP4ssw0rd

然后将 apk 解压一下(用的 bandizip,可以直接解压),在目录下面可以找到 \assets\flag.zip

解压即可拿到 flag

easy_mips (mips 逆向)

这是一道mips的题,用ida无法反汇编,爬了

先用ida的字符串搜索找到了关键代码在 tty_write 函数里,并且调用了 encry 函数和 init 函数,但因为没学过mips,也就没法继续分析了

于是第一次尝试使用 Ghidra

用 Ghidra 打开二进制文件

找到关键函数 tty_write

void tty_write(char *param_1,char *param_2)

{
  int __fd;
  size_t __n;
  int iVar1;
  EVP_PKEY_CTX aEStack56 [20];
  undefined4 local_24;
  undefined4 local_20;
  undefined4 local_1c;
  undefined4 local_18;
  undefined4 local_14;
  char local_10;
  int local_c;
  
  local_c = __stack_chk_guard;
  local_24 = 0x666c6167;
  local_20 = 0x7b69745f;
  local_1c = 0x69735f5f;
  local_18 = 0x5f5f6661;
  local_14 = 0x6b657d0a;
  local_10 = '\0';
  __fd = open(param_1,2);
  if (__fd < 0) {
    printf("cannot open");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  __n = strlen(param_2);
  write(__fd,param_2,__n);
  read(__fd,aEStack56,0x13);
  encry(aEStack56);
  iVar1 = strcmp((char *)aEStack56,(char *)&local_24);
  if (iVar1 == 0) {
    write(__fd,"you get the fake flag\n",0x16);
  }
  else {
    iVar1 = strcmp((char *)aEStack56,"3_isjA0UeQZcNa\\`\\Vf");
    if (iVar1 != 0) {
      puts("you_don\'t_get_the_flag");
      goto LAB_00400b48;
    }
    write(__fd,"good_job!\n",10);
  }
  close(__fd);
LAB_00400b48:
  if (local_c != __stack_chk_guard) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

之前在 ida 中发现 local_24 是一个fake flag,就不管了

发现调用了一个 encry 函数,进入这个函数

void encry(EVP_PKEY_CTX *param_1)

{
  char cVar1;
  int local_10;
  
  init(param_1);
  cVar1 = '\x05';
  local_10 = 0;
  while (local_10 < 0x13) {
    param_1[local_10] = (EVP_PKEY_CTX)((char)param_1[local_10] - cVar1);
    cVar1 = cVar1 + '\x01';
    local_10 = local_10 + 1;
  }
  return;
}

发现先调用了 init 函数,然后对每一位做了个减法

查看 init 函数

int init(EVP_PKEY_CTX *ctx)

{
  int iVar1;
  char extraout_var;
  char extraout_var_00;
  char extraout_var_01;
  int local_20;
  
  iVar1 = __stack_chk_guard;
  srand(0x1c5e);
  rand();
  rand();
  rand();
  *ctx = (EVP_PKEY_CTX)((byte)*ctx ^ extraout_var + 0x32U);
  ctx[5] = (EVP_PKEY_CTX)((byte)ctx[5] ^ extraout_var_00 - 0x39U);
  ctx[6] = (EVP_PKEY_CTX)((byte)ctx[6] ^ extraout_var_01 + 0x30U);
  local_20 = 7;
  while (local_20 < 0xd) {
    local_20 = local_20 + 1;
  }
  if (iVar1 != __stack_chk_guard) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return __stack_chk_guard;
}

这里自动分析出现了问题,不知道 extraout_varrand 有什么关系

于是只能去找对应的汇编慢慢读

根据 Ghidra 里代码与汇编的映射关系,找到了 extraout_var 的位置

        00400d00 80  43  00  00    lb         v1,0x0 (v0)
        00400d04 8f  c2  00  24    lw         v0,local_1c (s8)
        00400d08 30  42  00  ff    andi       v0,v0,0xff
        00400d0c 24  42  00  32    addiu      v0,v0,0x32
        00400d10 30  42  00  ff    andi       v0,v0,0xff
        00400d14 7c  02  14  20    seb        v0,v0
        00400d18 00  62  10  26    xor        v0,v1,v0
        00400d1c 7c  02  1c  20    seb        v1,v0
        00400d20 8f  c2  00  1c    lw         v0,local_24 (s8)
        00400d24 a0  43  00  00    sb         v1,0x0 (v0)

找到 addiu 说明 v0 对应的就是 extraout_var,发现是从 local_1c 处取出的

往上找 local_1c

        00400c80 8f  dc  00  10    lw         gp,local_30 (s8)
        00400c84 8f  82  80  44    lw         v0,-0x7fbc (gp)=>->rand                          = 004010e0
        00400c88 00  40  c8  25    or         t9,v0,zero
        00400c8c 03  20  f8  09    jalr       t9=>rand                                         int rand(void)
        00400c90 00  00  00  00    _nop
        00400c94 8f  dc  00  10    lw         gp,local_30 (s8)
        00400c98 00  40  18  25    or         v1,v0,zero
        00400c9c 83  c2  00  31    lb         v0,local_10 +0x1 (s8)
        00400ca0 00  62  10  26    xor        v0,v1,v0
        00400ca4 00  02  16  03    sra        v0,v0,0x18
        00400ca8 af  c2  00  24    sw         v0,local_1c (s8)

从后往前看,最后是将 v0 存到 local_1c 中,倒数第二步是将 v0 右移 0x18 位(这里问了一下会mips的大佬…),虽然没法完美分析出 rand 的结果存到了哪里,但是可以猜测最后右移了 0x18 位并存到了 local_1c 中。

于是就可以尝试编写代码,破解flag了。

首先计算几个 rand 的结果

#include <stdio.h>
#include <stdlib.h>

int main(){
    srand(0x1c5e);
    char a[20];
    a[0] = '8';
    printf("%x\n", rand());
    printf("%x\n", rand());
    printf("%x\n", rand());
    return 0;
}

运行得到

./test_rand
446aef60
5de30bb4
27445d71

随后写个反向的算法

encry = b'3_isjA0UeQZcNa\\`\\Vf'
flag_1 = []
t = 5
for i in encry:
    flag_1.append(i + t)
    t += 1
print (flag_1)

rand = [0x44 + 0x32, 0,0,0,0,0x5d - 0x39, 0x27 + 0x30] + [0] * 15
flag = ''
for i in range(len(flag_1)):
    flag += chr(flag_1[i] ^ (rand[i] & 0xff))

print (flag)

得到最终的flag

[56, 101, 112, 123, 115, 75, 59, 97, 114, 95, 105, 115, 95, 115, 111, 116, 113, 108, 125]
Nep{solar_is_sotql}

worrrrms (go语言逆向)

只知道用了SM4算法加密,不会go语言,具体的看不懂,爬了。

总结

这个比赛的前五道题其实就是各个方向的入门题目,有相应的工具并且能够看懂伪代码就可以做了,只需要进行静态分析。做go语言的时候发现go的函数调用太奇怪了,而且有各种指针,不愧是最安全的语言。

至于后面的几道题,由于解的人太少,也就没有去看,看完wp再补。

Built with Hugo
Theme Stack designed by Jimmy