0xGame 2024 Week 1&2 Reverse 详解
1388426792284542 发表于 安徽 CTF 926浏览 · 2024-10-18 07:51

[Week 1] BabyBase

文件为一个exe文件,直接运行一下,是让输入flag先随便输入看看有什么回显

有回显,但瞬间就结束了程序,下一步对其查壳分析一下程序

知道了是一个64位的程序,直接用IDA打开来分析它的伪代码

F5直接定位到它的主函数

通过分析上面的伪代码可以得知我们输入的flag被Str接收,v6是它的长度且要是42位,Str又被encode函数当成参数,猜测这里应该是加密flag,点开看看

__int64 __fastcall encode(__int64 a1, __int64 a2, int a3)
{
  __int64 result; // rax
  __int16 v4; // [rsp+4h] [rbp-Ch]
  unsigned __int8 v5; // [rsp+6h] [rbp-Ah]
  char v6; // [rsp+7h] [rbp-9h]
  int j; // [rsp+8h] [rbp-8h]
  int i; // [rsp+Ch] [rbp-4h]

  v4 = 0;
  v5 = 0;
  v6 = 61;
  for ( i = 0; ; ++i )
  {
    result = (unsigned int)(3 * i);
    if ( a3 <= (int)result )
      break;
    for ( j = 0; j <= 2; ++j )
    {
      if ( a3 <= 3 * i + j )
        *((_BYTE *)&v4 + j) = 0;
      else
        *((_BYTE *)&v4 + j) = *(_BYTE *)(a1 + 3 * i + j);
    }
    *(_BYTE *)(a2 + 4 * i) = table[(unsigned __int8)v4 >> 2];
    *(_BYTE *)(a2 + 4 * i + 1i64) = table[((unsigned __int8)(16 * v4) | (HIBYTE(v4) >> 4)) & 0x3F];
    if ( HIBYTE(v4) )
      *(_BYTE *)(a2 + 4 * i + 2i64) = table[((unsigned __int8)(4 * HIBYTE(v4)) | (v5 >> 6)) & 0x3F];
    else
      *(_BYTE *)(a2 + 4 * i + 2i64) = v6;
    if ( v5 )
      *(_BYTE *)(a2 + 4 * i + 3i64) = table[v5 & 0x3F];
    else
      *(_BYTE *)(a2 + 4 * i + 3i64) = v6;
  }
  return result;
}

分析可得这就是一个Base64的加密过程,table是码表

此时大致已经可以知道了这道题的考点了,就是实现了一个Base64的加密过程,加密后的flag要与check_flag函数中的对比一样的才是正确的flag所以只要对"MHhHYW1le04wd195MHVfa24wd19CNHNlNjRfRW5jMGQxbmdfdzNsbCF9"进行Base64解密即可

总结:
这道题主要是要明白Base64的加密原理,为了让各位师傅们明白的清楚一点我用python实现一下Base64加解密原理:

#加密
def b64encode(str):
    b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    b = ''
    Mi = ''
    for i in  str:
        b += format(ord(i),'08b') #在python中把一个数转换成二进制的形式的时候输出来的不是完整的8位python会把前面的零给抹除,所以要自己补,format这个函数比较实用'08b'的意思就是说把一个数转换成二进制不加'0b'前缀,不足8位在前面补零。
    if len(b) % 6 != 0:
        b = b + '0' * (6 - len(b) % 6) #注意这里在后面补0
    for i in range(0, len(b), 6):
        Mi += b64[int(b[i:i+6], 2)]  # 这里就是转换了这里我没有在每6个前面补0因为也不会影响大小
    if len(Mi) % 4 != 0:
        Mi = Mi + '=' * (4 - len(Mi) % 4)   # 补'='
    return Mi

# 解密:
def b64decode(str):
    b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    b = ''
    key = ''
    for i in range(1,3):
        str = list(str)     # 字符串是不能进行删除操作的所以要转换成列表
        if str[len(str)-i] == '=':
            str.remove('=')    # 删除'='
        str = ''.join(str)   # 转换成字符串 
    for i in str:
        b += format(b64.index(i), '06b') # 这里只要为6个就行,因为有两个是补的也不会影响大小
    if len(b) % 8 != 0:
        k = len(b) % 8
        b = b[::-1][k:][::-1] # 这一步是为了去除补在后面的0
    for i in range(0, len(b), 8):
        key += chr(int(b[i:i + 8], 2)) # 解码
    return key

[Week 1] BinaryMaster

题目提示:你做好准备成为二进制的大师了吗?

文件也是为一个exe文件,直接运行一下,是让输入 04242424的16进制先输入看看有什么回显,有个flag一闪而过并且结束了程序,看看输入其他的会怎么样没有结束程序,说明flag就在这个程序里面

查壳看看有没有壳

没有壳,直接用IDA打开,F5查看主函数

总结:签到题

[Week 1] SignSign

文件也是为一个exe文件,直接运行一下,看到了一半的flag,说明要在程序内部找到另外一半flag

查壳:

无壳,直接用IDA打开,F5定位到主函数

没有看到另外一半flag,那就看看整个程序的字符信息,快捷键Shift+F12

这就找到了另一半flag

总结:签到题

[Week 1] Xor-Beginning

题目提示:就从xor开始吧

文件为一个exe文件,直接运行一下,是让其输入flag,先随便输入,看看会回显什么

回显错误,还是得进程序内部看看,先进行查壳

无壳,直接用IDA打开,F5定位到主函数

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4[64]; // [rsp+20h] [rbp-70h] BYREF
  char v5[40]; // [rsp+60h] [rbp-30h] BYREF
  int v6; // [rsp+88h] [rbp-8h]
  int v7; // [rsp+8Ch] [rbp-4h]

  _main(argc, argv, envp);
  v7 = 0;
  v6 = 0;
  qmemcpy(v5, "~5\v*',3", 7);//前七位
  v5[7] = 31;
  v5[8] = 118;
  v5[9] = 55;
  v5[10] = 27;
  v5[11] = 114;
  v5[12] = 49;
  v5[13] = 30;
  v5[14] = 54;
  v5[15] = 12;
  v5[16] = 76;
  v5[17] = 68;
  v5[18] = 99;
  v5[19] = 114;
  v5[20] = 87;
  v5[21] = 73;
  v5[22] = 8;
  v5[23] = 69;
  v5[24] = 66;
  v5[25] = 1;
  v5[26] = 90;
  v5[27] = 4;
  v5[28] = 19;
  v5[29] = 76;
  printf("你的flag是什么? \n请在这里输入:");
  scanf("%s", v4);//接收输入
  while ( v4[v7] )
  {
    v4[v7] ^= 78 - (_BYTE)v7;//进行加密
    ++v7;
  }
  while ( v6 < v7 )
  {
    if ( v4[v6] != (unsigned __int8)v5[v6] || v7 != 30 ) //判断长度与是否加密后是否与v5相同
    {
      printf("\n错误, 请重试! ");
      system("pause");
      exit(0);
    }
    ++v6;
  }
  puts("\n正确!");
  system("pause");
  return 0;
}

根据上面分析可得:只要获取v5的值然后对加密进行逆转就行,flag的长度位30

一个一个收集v5的值比较麻烦,这时候就要动调了,因为这是一个exe文件所以用下面这个

下断点,在存入v5后都行

双击点v5,全部选中后Shift+E收集

现在有了数据就可以写脚本了

key = [0x7E, 0x35, 0x0B, 0x2A, 0x27, 0x2C, 0x33, 0x1F, 0x76, 0x37,
  0x1B, 0x72, 0x31, 0x1E, 0x36, 0x0C, 0x4C, 0x44, 0x63, 0x72,
  0x57, 0x49, 0x08, 0x45, 0x42, 0x01, 0x5A, 0x04, 0x13, 0x4C]
flag = ''
for i in range(len(key)):
    flag += chr(key[i]^(78-i))
print(flag)

总结:只要明白一些IDA的基本操作,与反异或就行:a^b=c --> c^b=a -->a^c=b

[Week 1] Xor-Endian

题目提示:刚开始就end了吗?

是一个Linux程序,先对其进行查壳

无壳,直接用IDA打开,F5定位到主函数

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+8h] [rbp-78h]
  int v6[12]; // [rsp+10h] [rbp-70h]
  _DWORD v7[14]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v8; // [rsp+78h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  puts(s);
  __isoc99_scanf(&unk_201F, v7);//接收输入的flag
  v6[0] = 1363025275;
  v6[1] = 253370901;
  v6[2] = 1448151638;
  v6[3] = 1415391232;
  v6[4] = 91507463;
  v6[5] = 139743552;
  v6[6] = 1450318164;
  v6[7] = 1985283101;
  v6[8] = 1465125718;
  v6[9] = 1934953223;
  v6[10] = 84430593;
  v6[11] = 0;
  encrypt(v7, "Key0xGame2024", 44LL, 13LL);//关键函数
  for ( i = 0; i <= 11; ++i )//对加密后的flag进行判断
  {
    if ( v7[i] - v6[i] )
    {
      puts("wrong!");
      return 0;
    }
  }
  puts("right!");
  return 0;
}
__int64 __fastcall encrypt(__int64 a1, __int64 a2, int a3, int a4)
{
  int i; // [rsp+24h] [rbp-4h]

  for ( i = 0; i < a3; ++i )
    *(_BYTE *)(i + a1) ^= *(_BYTE *)(i % a4 + a2);//进行异或加密,但是要注意数据类型
  return 0LL;
}

加密后的flag数据类型是char,v6是小端序的,解密只要对其进行反异或即可

一个一个收集v6的值也比较麻烦那就动调收集,不动调操作不了

与exe文件操作不同,要先在IDA中找到linux_server64,放入Linux系统里面,还有要把程序文件放进去与其在同一目录下

然后按照以下操作

下断点

动调

输入值

F8单步步出,到下面即可,就是把v6的值存储完

双击v6,然后获取值

解密:

key = 'Key0xGame2024'
hot = [ 0x7B, 0x1D, 0x3E, 0x51, 0x15, 0x22, 0x1A, 0x0F, 0x56, 0x0A,
  0x51, 0x56, 0x00, 0x28, 0x5D, 0x54, 0x07, 0x4B, 0x74, 0x05,
  0x40, 0x51, 0x54, 0x08, 0x54, 0x19, 0x72, 0x56, 0x1D, 0x04,
  0x55, 0x76, 0x56, 0x0B, 0x54, 0x57, 0x07, 0x0B, 0x55, 0x73,
  0x01, 0x4F, 0x08, 0x05]
flag = ''
for i in range(len(hot)):
    flag += chr(hot[i] ^ ord(key[i%len(key)]))
print(flag)

总结:运用了小端序的,远程联调,异或
补充:小端序将数据的低位字节存放在内存的低地址处,而高位字节则存放在内存的高地址处,更符合计算机读取内存的方式,因为CPU读取内存中的数据时,通常是从低地址向高地址方向进行的。

[Week 2] BabyUPX

题目提示:牢不可破 ?)的外壳

根据题目提示说明这个程序加了壳,对其进行查壳看看是什么类型的壳

一个简单的压缩壳,破壳可以使用两种方式一种是直接用脚本工具,另一种是自己脱壳,第一种有点无脑,那就用第二种,需要用到dbg调试器

压缩壳有个特性就是会自动解压,也就是说我们只要把解压后的程序保存下来就是脱壳的了,所以就需要找到那一部分

找到压栈入口

出栈时也会运行到这里我们只要在这下个硬件断点即可

运行到这:

转到内存窗口

下断点


运行,达到下一次到这

运行到出栈后

然后保存为一个新文件

成功

用IDA打开

看到没有什么信息,Shift+F12看看程序字符

看到类似flag的信息,双击进入,然后交叉引用

定位到了这

__int64 sub_4015B5()
{
  char v1[60]; // [rsp+20h] [rbp-40h] BYREF
  int v2; // [rsp+5Ch] [rbp-4h]

  sub_401720();
  v2 = 0;
  sub_402B60(aInputFlagHere);
  sub_402B58("%s", v1);//输入flag
  v2 = sub_402B38(v1, a0xgame, 6i64);//判断前面一部分
  if ( !v2 && sub_402B40(v1) == 44 ) //判断flag的长度与前面一部分是否正确
  {
    sub_401550(v1);//加密
    if ( !(unsigned int)sub_402B48(v1, qword_403020) ) //判断加密后的flag是否正确
      sub_402B68(aSucceed);
    return 0i64;
  }
  else
  {
    sub_402B68(aInvalid);
    return 0i64;
  }
}

主要函数在于sub_401550

__int64 __fastcall sub_401550(_BYTE *a1)
{
  _BYTE *v2; // [rsp+8h] [rbp-8h]

  v2 = a1;
  if ( !*a1 )
    return 0xFFFFFFFFi64;
  do
  {
    *v2 = (16 * *v2) | (*v2 >> 4);
    ++v2;
  }
  while ( *v2 );
  return 0i64;
}

分析可得加密方式为字节倒转,就是把一个字节的后4位与前4位调换了位置,所以只要把它调换回来就是flag,加密后的数据是qword_403020

解密:

def swap_nibbles(byte):
 return ((byte & 0x0F) << 4) | ((byte & 0xF0) >> 4)

data = [
0x03, 0x87, 0x74, 0x16, 0xD6, 0x56, 0xB7, 0x63, 0x83, 0x46,
0x66, 0x66, 0x43, 0x53, 0x83, 0xD2, 0x23, 0x93, 0x56, 0x53,
0xD2, 0x43, 0x36, 0x36, 0x03, 0xD2, 0x16, 0x93, 0x36, 0x26,
0xD2, 0x93, 0x73, 0x13, 0x66, 0x56, 0x36, 0x33, 0x33, 0x83,
0x56, 0x23, 0x66, 0xD7
]

swapped_data = [swap_nibbles(byte) for byte in data]
result = ''.join(chr(byte) for byte in swapped_data)

print("交换后的字符串:")
print(result)

总结:UPX壳,字节加密
补充:这里说一下魔改的UPX,就是把这个文件的数据改了,可以识别为UPX壳但脚本工具破不开,要把它给改回来,比如
正常的是下面这些

破壳不了的时候要注意这些

[Week 2] FirstSight-Jar

所给文件是一个jar文件,直接用jadx打开

package defpackage;

import java.util.Scanner;

/* renamed from: EzJar  reason: default package */
/* loaded from: EzJar.jar:EzJar.class */
public class EzJar {
    static String Alphabat = "0123456789abcdef";

    private static String encrypt(String str) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            int indexOf = Alphabat.indexOf(str.charAt(i));
            if (indexOf < 0) {
                sb.append(str.charAt(i));
            } else {
                sb.append(Alphabat.charAt(((indexOf * 5) + 3) % 16));
            }
        }
        return sb.toString();
    }

    public static void main(String[] strArr) {
        System.out.println("请以uuid的格式输入你的flag内容:");
        Scanner scanner = new Scanner(System.in);
        String nextLine = scanner.nextLine();
        scanner.close();
        if (encrypt(nextLine).equals("ab50e920-4a97-70d1-b646-cdac5c873376")) {
            System.out.println(String.format("验证成功: 0xGame{%s}", nextLine));
        } else {
            System.out.println("验证失败");
        }
    }
}

分析:这段代码是一个简单的加密验证程序,程序定义了一个静态字符串 Alphabat,包含16个字符,encrypt 方法是核心加密函数:它遍历输入字符串的每个字符,如果字符在 Alphabat 中,它会被替换:找到字符在 Alphabat 中的索引 indexOf用公式 ((indexOf * 5) + 3) % 16 计算新的索引用 Alphabat 中对应新索引的字符替换原字符如果字符不在 Alphabat 中,保持不变,main 方法是程序的入口:提示用户输入flag(格式为UUID)对输入进行加密比较加密结果是否等于 "ab50e920-4a97-70d1-b646-cdac5c873376"如果相等,验证成功;否则失败,所以我们需要逆向 encrypt 函数。
解密:

a = "0123456789abcdef"
k = "ab50e920-4a97-70d1-b646-cdac5c873376"
for i in range(0,len(k)):
    if k[i] in a:
        for j in range(16):
            if ((j * 5) + 3) % 16 == a.index(k[i]):
                print(a[j],end='')
    else:
        print(k[i],end='')

总结:就是一个加密程序,逆过来就行。

[Week 2] FisrtSight-Pyc

文件是一个pyc文件(是 Python 解释器在将 Python 源代码(.py 文件)编译成字节码后生成的文件),可以利用uncompyle6来反编译为py文件(uncompyle6 是一个强大的 Python 代码反编译器,它能够将 .pyc 文件(Python 字节码文件)转换回相应的 .py 源代码文件)
把编译的写入o.py里面

# uncompyle6 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.12.3 (tags/v3.12.3:f6650f9, Apr  9 2024, 14:05:25) [MSC v.1938 64 bit (AMD64)]
# Embedded file name: D:\PortPython3.8.9\App\Python\0xgame\EzPyc.py
# Compiled at: 2024-09-10 22:18:53
# Size of source mod 2**32: 519 bytes
import hashlib
user_input = input("请输入神秘代号:")
if user_input != "Ciallo~":
    print("代号不是这个哦")
    exit()
input_hash = hashlib.md5(user_input.encode()).hexdigest()
input_hash = list(input_hash)
for i in range(len(input_hash)):
    if ord(input_hash[i]) in range(48, 58):
        original_num = int(input_hash[i])
        new_num = (original_num + 5) % 10
        input_hash[i] = str(new_num)
    input_hash = "".join(input_hash)
    print("0xGame{{{}}}".format(input_hash))

直接运行会报错

给出的flag也是错的,还有一部分没有被加上

解密:

import hashlib
user_input = "Ciallo~"
input_hash = hashlib.md5(user_input.encode()).hexdigest()
input_hash = list(input_hash)
flag = ''
for i in range(len(input_hash)):
    if ord(input_hash[i]) in range(48, 58):
        original_num = int(input_hash[i])
        new_num = (original_num + 5) % 10
        flag += str(new_num)
    else:
       flag += input_hash[i]
print("0xGame{{{}}}".format(flag))

总结:python理解

[Week 2] Xor::Ramdom

题目提示:很幸运,你又遇到xor了,这次xor带来了一些随机数,(它保证这些随机数不是从外星来的)你能发现其中的奥秘吗?

是一个exe文件,运行一下

发现运行不了,说明这题不能动调,再查壳

无壳,IDA打开,F5定位到主函数

int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  char v4; // bl
  bool v5; // si
  bool v6; // bl
  _BYTE *v7; // rdx
  char v8; // al
  int v9; // ebx
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v13[4]; // [rsp+20h] [rbp-60h] BYREF
  char v14[32]; // [rsp+40h] [rbp-40h] BYREF
  char v15[46]; // [rsp+60h] [rbp-20h] BYREF
  char v16; // [rsp+8Eh] [rbp+Eh] BYREF
  char v17; // [rsp+8Fh] [rbp+Fh] BYREF
  char v18[32]; // [rsp+90h] [rbp+10h] BYREF
  char v19[32]; // [rsp+B0h] [rbp+30h] BYREF
  char v20[39]; // [rsp+D0h] [rbp+50h] BYREF
  char v21; // [rsp+F7h] [rbp+77h]
  int inited; // [rsp+F8h] [rbp+78h]
  int v23; // [rsp+FCh] [rbp+7Ch]

  _main(argc, argv, envp);
  inited = 0;
  v23 = 0;
  v21 = 0;
  std::allocator<char>::allocator(&v16);
  std::string::basic_string(v15, 40i64, 0i64, &v16);
  std::allocator<char>::~allocator(&v16);
  std::allocator<char>::allocator(&v17);
  std::string::basic_string(v14, 32i64, 0i64, &v17);
  std::allocator<char>::~allocator(&v17);
  v13[0] = 0x1221164E1F104F0Ci64;
  v13[1] = 0x171F240A4B10244Bi64;
  v13[2] = 0x1A2C5C2108074F09i64;
  v13[3] = 99338668810000i64;
  v3 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Input flag here:");
  std::ostream::operator<<(v3, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
  std::operator>><char>(refptr__ZSt3cin, v15);
  v4 = 0;
  v5 = 1;
  if ( std::string::size(v15) == 38 )
  {
    std::string::substr(v18, v15, 0i64, 7i64);
    v4 = 1;
    if ( !(unsigned __int8)std::operator!=<char>(v18, "0xGame{") && *(_BYTE *)std::string::operator[](v15, 37i64) == 125 )
      v5 = 0;
  }
  if ( v4 )
    std::string::~string(v18);
  if ( v5 )
    exit(0);
  std::string::substr(v19, v15, 7i64, 30i64);
  std::string::operator=(v14, v19);
  std::string::~string(v19);
  inited = init_random();
  std::string::basic_string(v20, v14);
  v6 = (unsigned int)check(v20) != 0;
  std::string::~string(v20);
  if ( v6 )
  {
    srand(0x1919810u);
    inited = rand();
  }
  v21 = rand();
  do
  {
    v7 = (_BYTE *)std::string::operator[](v14, v23);
    if ( (v23 & 1) != 0 )
      v8 = v21;
    else
      v8 = v21 + 3;
    *v7 ^= v8;
    v9 = *(char *)std::string::operator[](v14, v23);
    if ( v9 != *(unsigned __int8 *)std::array<unsigned char,32ull>::operator[](v13, v23) )
    {
      v10 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.Try again");
      std::ostream::operator<<(v10, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
      exit(0);
    }
    ++v23;
  }
  while ( v23 <= 29 );
  v11 = std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right!");
  std::ostream::operator<<(v11, refptr__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
  std::string::~string(v14);
  std::string::~string(v15);
  return 0;
}
int init_random(void)
{
  srand(0x77u);
  return rand();
}
_BOOL8 __fastcall check(__int64 a1)
{
  return std::string::size(a1) != 30;
}

分析:程序要求输入一个 38 字符长的字符串,必须以 "0xGame{" 开头,以 "}" 结尾,中间包含 30 个字符,这是实际需要验证的部分,随机数生成:使用 init_random() 函数初始化随机数生成器,种子固定为 0x77(119)。
check() 函数检查提取的字符串是否为 30 个字符,这里总是返回 false,由于 check() 返回 false,程序使用 init_random() 初始化的随机数序列。
核心验证逻辑:程序使用一个预设的 32 字节数组 v13 作为验证基准,遍历输入的 30 个字符,对每个字符进行异或操作:奇数索引位置与 v21 异或,偶数索引位置与 v21 + 3 异或,异或后的结果与 v13 数组中对应位置的字节比较,这里也是小端序的,解密只要反异或就行,关键在于随机数的生成。

解密:

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

int init_random(void)
{
    srand(0x77u);
    return rand();
}

int main() {
    unsigned char v13[] = {
        0x0C, 0x4F, 0x10, 0x1F, 0x4E, 0x16, 0x21, 0x12,
        0x4B, 0x24, 0x10, 0x4B, 0x0A, 0x24, 0x1F, 0x17,
        0x09, 0x4F, 0x07, 0x08, 0x21, 0x5C, 0x2C, 0x1A,
        0x10, 0x1F, 0x11, 0x16, 0x59, 0x5A
    };

    init_random();  // 第一次调用rand(),结果被丢弃
    char v21 = rand();  // 第二次调用rand(),结果用于异或操作

    char flag[31] = {0};
    for (int v23 = 0; v23 <= 29; v23++)
    {
        unsigned char v8 = (v23 & 1) != 0 ? v21 : v21 + 3;
        flag[v23] = v13[v23] ^ v8;
    }

    printf("0xGame{%s}\n", flag);
    return 0;
}

总结:主要还是要搞懂代码意思,加密方式并不难。

[Week 2] ZzZ

题目提示:怎么这些函数名字长得都一样啊,已经看的要昏昏欲睡了,听说如果能找到失散的main函数,就有机会用神器Solver解开一切的谜团。

是一个exe文件,运行一下

运行不了,不能动调,查壳

无壳,直接用IDA打开

没有main函数,那就去看看字符

有类似flag的信息,直接交叉引用定位

__int64 sub_140011AA0()
{
  char *v0; // rdi
  __int64 i; // rcx
  char v3[32]; // [rsp+0h] [rbp-40h] BYREF
  char v4; // [rsp+40h] [rbp+0h] BYREF
  unsigned int v5[8]; // [rsp+44h] [rbp+4h] BYREF
  unsigned int v6[8]; // [rsp+64h] [rbp+24h] BYREF
  unsigned int v7[9]; // [rsp+84h] [rbp+44h] BYREF
  char *Buffer; // [rsp+A8h] [rbp+68h]
  char v9[80]; // [rsp+C8h] [rbp+88h] BYREF
  __int64 v10; // [rsp+118h] [rbp+D8h]
  __int64 v11; // [rsp+138h] [rbp+F8h]
  __int64 v12; // [rsp+158h] [rbp+118h]
  __int64 v13[4]; // [rsp+178h] [rbp+138h] BYREF
  __int64 v14[26]; // [rsp+198h] [rbp+158h] BYREF

  v0 = &v4;
  for ( i = 94i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 += 4;
  }
  sub_140011393(&unk_140023008);
  sub_1400111A9("Please enter your flag\nTip: The flag format is 'uuid'\n");
  sub_1400110A0("%44s", v9);//输入flag
  if ( v9[43] != 125 )
    goto LABEL_11;
  sub_14001128F(v9, "0xGame{%8llx-%4s-%4s-%4s-%12llx}", v13, (const char *)v5, (const char *)v6, (const char *)v7, v14);
  v10 = v5[0];
  v11 = v6[0];
  v12 = v7[0];
  if ( v14[0] == 0xD085A85201A4i64 )
  {
    if ( 11 * v11 + 14 * v10 - v12 == 0x48FB41DDDi64
      && 9 * v10 - 3 * v11 + 4 * v12 == 0x2BA692AD7i64
      && ((unsigned __int64)(v12 - v11) >> 1) + (v10 ^ 0x87654321i64) == 3451779756 )
    {
      Buffer = "Congratulations! You get the correct flag.";//要到达这里
      if ( v13[0] == 3846448765i64 )
      {
        puts(Buffer);
        goto LABEL_12;
      }
    }
LABEL_11:
    sub_1400112BC(v9);
  }
LABEL_12:
  sub_14001132F(v3, &unk_14001AED0);
  return 0i64;
}

这个应该就是main了
分析:程序要求输入一个44字符长的字符串,最后一个字符(索引43)必须是'}',输入格式应该符合UUID格式,
逻辑:使用sscanf类似的函数(sub_14001128F)解析输入,格式为:"0xGame{%8llx-%4s-%4s-%4s-%12llx}"解析出5个部分:v13, v5, v6, v7, v14
验证逻辑:检查v14[0]是否等于0xD085A85201A4进行三个数学运算验证:
a) 11 v11 + 14 v10 - v12 == 0x48FB41DDD
b) 9 v10 - 3 v11 + 4* v12 == 0x2BA692AD7
c) ((v12 - v11) >> 1) + (v10 ^ 0x87654321) == 3451779756
检查v13[0]是否等于3846448765

解密:

#  运用Z3约束器
from z3 import *

s = Solver()

# 定义64位的位向量变量
v5 = BitVec('v5', 64)
v6 = BitVec('v6', 64)
v7 = BitVec('v7', 64)

# 添加约束条件,使用位向量操作
s.add(11 * v6 + 14 * v5 - v7 == 0x48FB41DDD)
s.add(9 * v5 - 3 * v6 + 4 * v7 == 0x2BA692AD7)
s.add(LShR(v7 - v6, 1) + (v5 ^ 0x87654321) == 3451779756)

if s.check() == sat:
    model = s.model()
    print(f"v5 = {model[v5]}")
    print(f"v6 = {model[v6]}")
    print(f"v7 = {model[v7]}")
else:
    print("No solution found.")
v13 = 3846448765
v14 = 0xD085A85201A4
v5 = 842086455
v6 = 862073908
v7 = 1681208161

# 将整数转换为4字符的字符串
def int_to_str(num):
    return ''.join([chr((num >> (8*i)) & 0xFF) for i in range(4)])

# 格式化flag
flag = "0xGame{%8x-%4s-%4s-%4s-%012x}" % (
    v13,
    int_to_str(v5),
    int_to_str(v6),
    int_to_str(v7),
    v14
)

print(flag)

总结:Z3的运用,关键函数的找寻。
补充:UUID标准格式:8-4-4-4-12 字符,例如:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。

0 条评论
某人
表情
可输入 255