Flare on 2018 Writeup (1-6)

Flare On 2018 Writeup [1-6]

加班之余抽空练练手,lowkey要带我玩flare-on, 趁机正好看看以前的题目,直接官网上就能下载到所有以前的题目。有些题写的比较多,放一起太长了,拆成两份笔记来做。

MinesweeperChampionshipRegistration.jar

一个java文件,直接上jd-gui,打开一看

GoldenTicket2018@flare-on.com

UltimateMinesweeper.exe

一个.net的文件,直接上dnspy,核心就是MainForm这个表单,基本逻辑实现了一个只能点击一次的扫雷程序,很容易可以看到里面有个getkey函数

private string GetKey(List<uint> revealedCells)
{
    revealedCells.Sort();
    Random random = new Random(Convert.ToInt32(revealedCells[0] << 20 | revealedCells[1] << 10 | revealedCells[2]));
    byte[] array = new byte[32];
    byte[] array2 = new byte[]
    {
        245,
        75,
        65,
        142,
        68,
        71,
        100,
        185,
        74,
        127,
        62,
        130,
        231,
        129,
        254,
        243,
        28,
        58,
        103,
        179,
        60,
        91,
        195,
        215,
        102,
        145,
        154,
        27,
        57,
        231,
        241,
        86
    };
    random.NextBytes(array);
    uint num = 0u;
    while ((ulong)num < (ulong)((long)array2.Length))
    {
        byte[] expr_5B_cp_0 = array2;
        uint expr_5B_cp_1 = num;
        expr_5B_cp_0[(int)expr_5B_cp_1] = (expr_5B_cp_0[(int)expr_5B_cp_1] ^ array[(int)num]);
        num += 1u;
    }
    return Encoding.ASCII.GetString(array2);
}

长着一脸flag样,搜索一下看看调用

这个在构造函数里可以看到这个SquareRevealedCallback实际上是扫雷的时候点击一个方块的回调函数,然后this.RevealedCells实际上存储着row*30+column的值,然后满足一定条件后将this.RevealedCells传给之前的getkey函数,然后调用SuccessPopup展示出来。

然后我们仔细看看getkey函数,这个函数先将传入的this.RevealedCells数组进行升序排列,然后把数组的最小的三个进行一个处理

revealedCells[0] << 20 | revealedCells[1] << 10 | revealedCells[2])

然后将这个值作为随机的种子随机生成32个字节,然后和固定的array2进行异或得到结果,那就很简单了,我们爆破3个数就行了,每个数的最大值不超过30*30+30,然后在结果里面搜索flare-on.com就可以了。

这里直接复制粘贴一下,用vs将就着c#来写了,主要代码如下:

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            FileStream F = new FileStream("E:\\test.dat",
            FileMode.OpenOrCreate, FileAccess.ReadWrite);
            InitializeComponent();
            for (uint i = 1; i <= 930; i++)
            {

                for (uint j = 1; j <= i; j++)
                {
                    for (uint k = 1; k <= j; k++)
                    {
                        string a = GetKey(k,j,i);
                        F.Write(System.Text.Encoding.Default.GetBytes(a),0,a.Length);
                    }
                }
                Console.WriteLine(i);
            }
            F.Close();
            int c=1 + 1;
        }

        private string GetKey(uint a1,uint a2,uint a3)
        {
            //revealedCells.Sort();
            //Random random = new Random(Convert.ToInt32(revealedCells[0] << 20 | revealedCells[1] << 10 | revealedCells[2]));
            Random random = new Random(Convert.ToInt32(a1 << 20 | a2<< 10 | a3));
            byte[] array = new byte[32];
            byte[] array2 = new byte[]{
                245,
                75,
                65,
                142,
                68,
                71,
                100,
                185,
                74,
                127,
                62,
                130,
                231,
                129,
                254,
                243,
                28,
                58,
                103,
                179,
                60,
                91,
                195,
                215,
                102,
                145,
                154,
                27,
                57,
                231,
                241,
                86
            };
            random.NextBytes(array);
            uint num = 0u;
            while ((ulong)num < (ulong)((long)array2.Length))
            {
                byte[] expr_5B_cp_0 = array2;
                uint expr_5B_cp_1 = num;
                expr_5B_cp_0[(int)expr_5B_cp_1] = (byte)(expr_5B_cp_0[(int)expr_5B_cp_1] ^ array[(int)num]);
                num += 1u;
            }
            return Encoding.ASCII.GetString(array2);
        }
    }
}

跑了两分钟得到一个4G的数据文件,grep一下拿到flag

FLEGGO

打开文件夹拿到一堆exe有点小懵逼,先随便搞一个来分析一下,核心代码:

看到第一个主要函数就是sub_401240,跟进看一下:

第一个wcscmp无关痛痒,主要第二个wcscmp,看看这个resource的值来源:

可以看到resource的值来源于一个名叫BRICK的资源,

大小正好0x8150,之后的对比发现各个文件之间的主要不同也在这个资源上。

那回到之前的对比,wcscmp会一直对比到\x00停止,以该样本1BpnGjHOT7h5vvZsV4vISSb60Xj3pX5G.exe为例,就是把从开头到\x00开始的值转为可显字符就是ZImIT7DyCMOeF6,所以密码就是这个。

然后发现生成了一个png,并打印了个w字母

那我们写个脚本批量跑一下:

from subprocess import *
import os
import re
dir = "E:\\FLEGGO"
dirs = os.listdir(dir)
for i in dirs:
    if '.exe' not in i:
        continue
    file=os.path.join(dir,i)
    data=open(file,'rb').read()[0x2ab0:0x2ab0+0x100]
    a=data[0:data.index('\x00\x00')].replace('\x00','')
    p = Popen( [file], stdin=PIPE, stdout=PIPE )
    p.stdin.write(a+'\n')
    result=p.stdout.read()
    r=re.findall('(.*? => .)',result)[0]
    print i,r

结果如下:

这每个exe得到的字母有点像乱序的flag。然后发现每个图片左上角有一个序号,例如1BpnGjHOT7h5vvZsV4vISSb60Xj3pX5G.exe得到的图片左上角为7,字母为w,意味着w在flag的第7位。

排序之后即可得到flag:mor3_awes0m3_th4n_an_awes0me_p0ssum@flare-on.com

binstall.exe

从这个题开始正式进入恶意软件的分析,拿到一个exe,然后看到是个.net,同样用dnspy打开:

被混淆过的代码基本不可读,用de4net解完混淆之后还是可读性很差。

先分析看看程序行为把,跑起来Process Monitor,然后监控到很多文件和注册表操作,然后开始寻找其中的可疑点:

在拉的时候看到了可疑点,这里修改了注册表项AppInit_DLLsLoadAppInit_DLLsRequireSignedAppInit_DLLs,这三个注册表项是win7进行DLL注入的关键表项,
设置恰当的值,就能达到凡是导入了user32.dll的程序都会主动加载AppInit_DLLs键值下的模块

  • LoadAppInit_DLLs: 为0时禁止AppInit_DLLs,为1时允许
  • AppInit_DLLs: 一个DLL文件的完整路径
  • RequireSignedAppInit_DLLs: 为0时加载任意dll文件,为1时只加载经过签名的dll文件。

那运行过恶意程序之后,打开注册表一看:
这三个值如下:

发现被注入了一个dll,直接去路径下寻找这个dll文件:

然后打开IDA,简单分析之后DllEntryPoint->sub_1000B2ED->sub_100027D0,
看到关键函数:

signed int __stdcall sub_100027D0(int a1, int a2, int a3)
{
  CHAR Filename; // [esp+0h] [ebp-11Ch]
  const char *v5; // [esp+104h] [ebp-18h]
  int v6; // [esp+108h] [ebp-14h]
  int v7; // [esp+10Ch] [ebp-10h]
  int v8; // [esp+110h] [ebp-Ch]
  const char *v9; // [esp+114h] [ebp-8h]

  v7 = a2;
  if ( a2 == 1 )
  {
    GetModuleFileNameA(0, &Filename, 0x104u);
    v8 = sub_10002640((int)&Filename, 0x5C) + 1;
    sub_100026C0(v8);
    v9 = (const char *)v8;
    v6 = v8 + 1;
    v9 += strlen(v9);
    v5 = &(++v9)[-v8 - 1];
    if ( crc32(v8, (unsigned int)&v9[-v8 - 1]) == 0x4932B10F && sub_10002710(&Filename) == 1 )
      CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, 0, 0, 0);
  }
  return 1;

首先获取当前进程直到.exe的绝对路径,然后在进sub_10002640进行处理,进sub_10002640->sub_1000BE60

  if ( (unsigned int)dword_10021C8C < 1 )
  {
    v25 = a1;
    v26 = -1;
    do
    {
      if ( !v26 )
        break;
      v19 = LOBYTE(v25->m128i_i32[0]) == 0;
      v25 = (__m128i *)((char *)v25 + 1);
      --v26;
    }
    while ( !v19 );
    v27 = -(v26 + 1);
    v28 = &v25[-1].m128i_i8[15];
    do
    {
      if ( !v27 )
        break;
      v19 = *v28-- == a2;
      --v27;
    }
    while ( !v19 );
    v29 = v28 + 1;
    if ( *v29 == a2 )
      result = (int)v29;
    else
      result = 0;
  }

分析之后发现循环找到最后一个a2出现的位置,a2是固定参数0x5c,换成字符就是\,也就是sub_1000BE60把程序文件名从绝对路径中取出来,然后再进入sub_100026C0

int __cdecl sub_100026C0(int a1)
{
  int result; // eax
  int i; // [esp+0h] [ebp-4h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
  {
    *(_BYTE *)(i + a1) = sub_10010F8F(*(char *)(i + a1));
    result = i + 1;
  }
  return result;
}

这里逐位调用了sub_10010F8F,而sub_10010F8F函数的功能就是把大写转小写。
然后接着回到sub_100027D0,接下来就是关键判断,判断成功之后就会创建一个新的线程:

if ( sub_10002660(v8, (unsigned int)&v9[-v8 - 1]) == 0x4932B10F && sub_10002710(&Filename) == 1 )
      CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, 0, 0, 0);

sub_10002660就是对之前取出的文件名进行一个crc32的计算,然后判断是否等于0x4932B10F,根据多方提示,写个代码尝试一下chrome.exe,explorer.exe,firefox.exe,最后尝试出是firefox.exe:

#include<stdio.h>
#include<string.h>

int main(){
    char *a1="firefox.exe";
    int a2=strlen(a1);
    unsigned int v4 = 0x5B4FFA86;
    for ( int i = 0; i < a2; ++i ){
        v4 = (*(char *)(i + a1) - 0x12477CE0) ^ (v4 >> 1);
    }
    printf("0x%08x\n", ~v4); 
    return ~v4;
}

也就是判断当前进程是否是firefox.exe,并且sub_10002710对firefox的版本进行了判断要小于55。
然后我们进入到这个新的线程函数进行分析,进到关键函数sub_10006E10

void sub_10006E10()
{
  DWORD pcbBinary; // [esp+0h] [ebp-10h]
  LPCSTR pszString; // [esp+4h] [ebp-Ch]
  BYTE *v2; // [esp+8h] [ebp-8h]
  BYTE *v3; // [esp+Ch] [ebp-4h]

  if ( !dword_10022650 )
  {
    v2 = sub_10004360();
    while ( !dword_10022650 )
    {
      pszString = (LPCSTR)sub_100028A0((LPCSTR)v2 + 4, (LPCSTR)v2 + 260);
      if ( pszString )
      {
        .....
        .....
        .....
      }
    }
  }
}

函数看着比较复杂,不过可以先简单分析,这里看到首先从sub_10004360函数获取了一个字符串,然后传递给sub_100028A0,在sub_100028A0利用v2发起了http请求:

所以我们回头来分析这个sub_10004360产生的字符串内容,但是突然一想,为啥不直接调呢。。。。
下了个firefox54,跑起来:

看到了这个链接,拼起来pastebin.com/raw/hvaru8N,访问一下。。emm失效了,
GG
这道题只能这样了。后面还有解密,不过如果链接有效的话,动态调跟进去应该就可以拿到原文。

看看官方wp,其中提到的NoFuserEx,对de4net解混淆后的程序再用NoFuserEx再解混淆一次,发现很多字符串直接还原出来了。

截了几个图:

总之可读性很高,新工具+1……

这道题就先到这里把

web2point0

一道wasm的逆向题,最近的CTF比赛也好像都喜欢出这个。
之前都是直接放进ida里面用idawasm搞,搞出来的汇编其实还是蛮多的,很多无用的取值赋值操作,昨儿查资料学到了一种新的方法。
当然还是用wabt工具包

第一步先是反编译:

./wasm2c test.wasm -o test.c

其实这个.c就能看了不过会耗费稍多一点时间,然后同样将wabt工具包里面wasm2c文件夹下wasm-rt.hwasm-rt-impl.cwasm-rt-impl.h三个文件放到和test.c同一个文件,
然后编译,这里就不需要链接了,因为链接肯定通不过了,所以我们只需要得到未链接的
文件即可:

gcc -c test.c -o test.o

然后把test.o拖到ida里面F5,就会出现可读性比较高的代码了。

这个题整体还是比较简单的:

a固定,b为用户输入,把a,b,len(a),len(b)传入test.wasmMatch函数,看看这个函数:

核心就是调用f9函数,然后检验返回值和传入的v4地址上的值
然后进f9,看似很长,其实核心就是这个:

根据当前ai的值与0xf异或,然后去栈的1024开始的地方取对应的值x

然后调用16*x+T0+8

所以逻辑理一下就是:

ai & 0xf==0: call f2
ai & 0xf==1: call f3
ai & 0xf==2: call f4
ai & 0xf==3: call f5
ai & 0xf==4: call f6
ai & 0xf==5: call f7
ai & 0xf==6: call f8

然后再看这每个函数的作用,发现就是很简单的数值运算,最后代码如下:

a=[0xE4, 0x47, 0x30, 0x10, 0x61, 0x24, 0x52, 0x21, 0x86, 0x40, 0xAD, 0xC1, 0xA0, 0xB4, 0x50, 0x22, 0xD0, 0x75, 0x32, 0x48, 0x24, 0x86, 0xE3, 0x48, 0xA1, 0x85, 0x36, 0x6D, 0xCC, 0x33, 0x7B, 0x6E, 0x93, 0x7F, 0x73, 0x61, 0xA0, 0xF6, 0x86, 0xEA, 0x55, 0x48, 0x2A, 0xB3, 0xFF, 0x6F, 0x91, 0x90, 0xA1, 0x93, 0x70, 0x7A, 0x06, 0x2A, 0x6A, 0x66, 0x64, 0xCA, 0x94, 0x20, 0x4C, 0x10, 0x61, 0x53, 0x77, 0x72, 0x42, 0xE9, 0x8C, 0x30, 0x2D, 0xF3, 0x6F, 0x6F, 0xB1, 0x91, 0x65, 0x24, 0x0A, 0x14, 0x21, 0x42, 0xA3, 0xEF, 0x6F, 0x55, 0x97, 0xD6]
b=[]

i=0
while i<len(a):
    if a[i]&0xf==0: #f2
        b.append(a[i+1]&0xff) 
        i+=1
    elif a[i]&0xf==1: #f3
        b.append(~(a[i+1])&0xff)
        i+=1
    elif a[i]&0xf==2: #f4
        b.append((a[i+1]^a[i+2])&0xff)
        i+=2
    elif a[i]&0xf==3: #f5
        b.append((a[i+1]&a[i+2])&0xff)
        i+=2
    elif a[i]&0xf==4: #f6
        b.append((a[i+1]|a[i+2])&0xff)
        i+=2
    elif a[i]&0xf==5: #f7
        b.append((a[i+1]+a[i+2])&0xff)
        i+=2
    elif a[i]&0xf==6: #f8
        b.append((a[i+2]-a[i+1])&0xff)
        i+=2
    else:
        pass
    i+=1

print("".join(chr(j) for j in b))

然后得到flag:

magic

拿到文件,拖进ida看看,

从输入开始看吧,大概逻辑很清晰,首先是如变量v13开始定义了69字节的数据,然后总共输入666次,将69字节的数据和我们的输入逐字节异或666次之后,得到最终答案。也就是我们得想办法求出我们输入的总共666次的数据分别是啥,然后异或就能得到结果。

获取了我们的输入之后进入了sub_402dcf函数(check函数),

这个函数本质上是一个动态解密加调用,在内存0x605100开始的0x120*33大小的内存空间中存储了330x120大小的块,每一个块是一个结构体,每一个结构体能够对输入的key值中的一部分进行校验,几个关键的属性如下:

0-4   :加密函数的地址
8-12  :加密函数的长度
12-16 :该块所校验的输入值中的起始位置
16-20 :该块所校验的输入值的长度
24-28 :异或数据的地址
32-   :函数的参数

check函数的流程就是将加密函数和异或数据进行异或得到可执行的解密函数,然后将函数参数传入进行执行,函数则会对用户输入的key值中的一部分进行校验,

换言之,从0x605100开始的33个块,每一个块对应一个校验函数,可以求出一部分输入的key值.

为了能更好的看函数体,我们先把它解密了:


data_offset = 0x605100-0x604e10+0x4e10

def decrypto():
    f=open("./magic",'rb')
    content=list(f.read())
    f.close()
    for i in range(0x21):
        cur_block=b"".join(i.to_bytes(1,"little") for i in content[data_offset+i*0x120:data_offset+(i+1)*0x120])
        func,=unpack("<I",cur_block[0:4])
        length,=unpack("<I",cur_block[8:12])
        xor_data_addr,=unpack("<I",cur_block[24:28])
        for i in range(length):
            xor_data_offset=xor_data_addr-0x604e10+0x4e10
            content[func-0x400000+i]=(content[func-0x400000+i]^content[xor_data_offset+i])
    f=open('./magic_de_1','wb')
    f.write(b"".join(i.to_bytes(1,"little") for i in content))
    f.close()

然后拖进ida里面,调用python script:

# -*- coding: utf-8 -*-
import idc
func=[
0x400c55 ,
0x401e1d ,
0x40166e ,
0x400e20 ,
0x4027a7 ,
0x4025e4 ,
0x402064 ,
0x401a34 ,
0x40111e ,
0x40272b ,
0x401498 ,
0x401cd6 ,
0x402555 ,
0x40179d ,
0x401721 ,
0x402c14 ,
0x401c5a ,
0x401f64 ,
0x400bc6 ,
0x40119a ,
0x401bd6 ,
0x401821 ,
0x4021ab ,
0x401b47 ,
0x4019b8 ,
0x401fe8 ,
0x400d9c ,
0x4024d1 ,
0x402acd ,
0x4018a5 ,
0x401527 ,
0x401929 ,
0x401ac3 ,
]
for i in func:
    idc.MakeFunction(i)

然后就能反编译出c语言看了,然后就是挨个看这33个函数。。。。。。

最后看完下来发现其实33个函数其实就是7个函数的循环调用,包括一些简单的异或、crc32、斐波那契数列等7个函数。用python实现如下:

def func1(ans,length): 
    res=[]
    for i in ans:
        res.append(i-13)
    return res

def func2(ans,length):
    length = len(ans)
    tmp=[]
    for i in range(0,length,8):
        if i+8<length:
            tmp.append(int.from_bytes(ans[i:i+8],'little'))
        else:
            tmp.append(int.from_bytes(ans[i:length],'little'))
    ans=tmp
    print(ans)
    res=[]
    for i in ans:
        v6 = 0
        v5 = 1
        v4 = 0
        cnt = 0
        while 1:
            v6 = (v4+v5)&0xffffffffffffffff
            v4 = v5
            v5 = v6
            cnt+=1
            #print(hex(v6),hex(i))
            if v6==i:
                res.append(cnt)
                break
    return res

def func3(ans,length):
    res=[]
    for i in ans:
        res.append(i^0x2a)
    return res

def func4(ans,length):

    res=[]
    v4 = binascii.a2b_hex('2074756220736954')[::-1]
    v4+=binascii.a2b_hex('6374617263732061')[::-1]
    v4+=binascii.a2b_hex('2e68')[::-1]
    v8=[]
    for i in range(0x100):
        v8.append(i)
    v11=0
    v12=0
    v13=0
    for i in range(0x100):
        v13 =(v13+(v8[i] + v4[i%18]))&0xff
        v8[i] ^= v8[v13]
        v8[v13] ^= v8[i]
        v8[i] ^= v8[v13]

    for i in range(len(ans)):
        v12+=1
        v11 = (v11+v8[v12])&0xff
        v8[v12] ^= v8[v11]
        v8[v11] ^= v8[v12]
        v8[v12] ^= v8[v11]
        v13 = (v8[v12] + v8[v11])&0xff
        res.append(ans[i]^v8[v13])

    return res

def func5(ans,length):
    res=list(ans)
    return res

def func6(ans,length):
    ans,=unpack('I',ans)
    res=[]
    guess=[]  
    def get_guess(tmp,cur):
        if len(cur)==length:
            tmp.append(cur)
            return
        for i in range(0xff):
            get_guess(tmp,cur+i.to_bytes(1,'little'))
        return
    get_guess(guess,b"")

    for i in guess:
        if binascii.crc32(i)==ans: 
            res=list(i) 
            break
    return res

def func7(ans,length):
    data=['2346A7C2645F392A',
        '42704D2847746B53',
        '4A4038626A522549',
        '5024312D59444569',
        '6671764C21547967',
        '304F57516D68632B',
        '6C336E75345A4E65',
        '4B7A617732264837']
    v13=b""
    v23=0
    res=[]
    guess=[]
    for i in data:
        v13+=binascii.a2b_hex(i)[::-1]
    def get_guess(tmp,cur):
        if len(cur)==length:
            tmp.append(cur)
            return
        for i in range(0xff):
            get_guess(tmp,cur+i.to_bytes(1,'little'))
        return
    get_guess(guess,b"")
    for k in guess:
        flag=1
        for i in range(length):
            if flag==0:
                break
            if i%3==0:
                tmp = k[i]>>2
                if ans[i] != v13[tmp]:
                    flag=0
                v23 = (k[i]&0x3)<<8
            if i%3==1:
                tmp = (k[i]+v23)>>4
                if ans[i] != v13[tmp]:
                    flag=0
                v23=(k[i]&0xf)<<8
            if i%3==2:
                tmp = (k[i]+v23)>>6
                if ans[i] != v13[tmp]:
                    flag=0
                tmp2 = (k[i]+v23)&0x3f
                if ans[i+1] != v13[tmp2]:
                    flag=0
        if flag==1:
            return list(k)

然后映射关系如下:

func_map={
    0x400c55:func2,
    0x401e1d:func2,
    0x40166e:func6,
    0x400e20:func4,
    0x4027a7:func7,
    0x4025e4:func2,
    0x402064:func2,
    0x401a34:func1,
    0x40111e:func5,
    0x40272b:func5,
    0x401498:func1,
    0x401cd6:func2,
    0x402555:func1,
    0x40179d:func3,
    0x401721:func5,
    0x402c14:func6,
    0x401c5a:func5,
    0x401f64:func3,
    0x400bc6:func1,
    0x40119a:func4,
    0x401bd6:func3,
    0x401821:func3,
    0x4021ab:func7,
    0x401b47:func1,
    0x4019b8:func5,
    0x401fe8:func5,
    0x400d9c:func3,
    0x4024d1:func3,
    0x402acd:func2,
    0x4018a5:func3,
    0x401527:func2,
    0x401929:func1,
    0x401ac3:func3
}

这样我们可以得到第一轮校验的结果:

def get_first_key():
    f=open("./magic",'rb')
    content=list(f.read())
    f.close()
    key=[0]*100
    this_turn=[]
    for i in range(0x21):
        cur_block=b"".join(i.to_bytes(1,"little") for i in content[data_offset+i*0x120:data_offset+(i+1)*0x120])
        func,=unpack("<I",cur_block[0:4])
        length,=unpack("<I",cur_block[8:12])
        xor_data_addr,=unpack("<I",cur_block[24:28])
        key_start,=unpack("<I",cur_block[12:16])
        key_len,=unpack("<I",cur_block[16:20])
        data=cur_block[32:0x120].strip(b'\x00')
        print(hex(key_start),",")
        continue
        tmp=func_map[func](data,key_len)
        print(hex(func),hex(key_start),hex(key_len),binascii.b2a_hex(data),"".join(chr(j) for j in tmp))
        this_turn.append("".join(chr(j) for j in tmp))
        for i in range(key_len):
            key[key_start+i]=tmp[i]
    print(key)
    print(this_turn)
    return this_turn

然后回到main函数,接下来由于我们输入的key校验成功了,接下来将加密的数据和我们的输入异或,之后进入sub_4037BF函数

该函数的核心就是打乱从0x605100开始的33个块的顺序,由于每个块对应一部分key值,即该函数是打乱key的顺序,并没有修改函数体本身,核心便是sub_40332D函数

分为两部分,即两个循环,第一个循环是重置每个块所决定的部分key在总体的key的位置,第二个循环则是打乱物理排列顺序,对当前的key的结果没有影响。

举个例子,第一轮我们得到的结果0x605100开始的第一个块block1的作用是校验输入的key值,从0x2开始的三个字符是不是为"ds ",那经过sub_40332D函数的第一个循环,使得这个block1的作用变成了校验输入的key值,从0x20开始的三个字符是不是为"ds ",然后经过第二个循环使得block1的位置从原来的第一个快,变成了第十个块。

这里是用的rand()函数来进行随机变换,但是在main函数中,进行了srand操作,并且seed是已知的,所以其随机数就可以预测,然后我们可以正向进行操作,生成每一轮所需要的key值,然后和加密数据异或便得到最终结果。
计算666个key值的脚本需要用c来写,因为语言不同,随机生成器也不尽相同。

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

void swap(char str[][4], int i, int j){
    char tmp[4];  
    memset(tmp,0,strlen(tmp));
    memcpy(tmp,str[i],strlen(str[i]));
    memset(str[i],0,strlen(str[i]));
    memcpy(str[i],str[j],strlen(str[j]));
    memset(str[j],0,strlen(str[j]));
    memcpy(str[j],tmp,strlen(tmp));
}

void swap_key(int index[],int i,int j){
    int tmp=index[i];
    index[i]=index[j];
    index[j]=tmp;
}


int main(){
    unsigned int v3;
    int seed=1596020546;
    srand(seed);
    char first_turn[33][4]={"ds ","in", ".", "ng ", " ", "hot", "e ", "the", " ", " in", "g", " ", "r", "e ", " ", "ace", "lik", "e", "in", "ll", "no", " H", " yo", "ur", "of", "f", "is", " bl", "ow", "thi", " w", "the", "Ah,"};
    int start_in_key[33]={0x2 , 0x2c , 0x10 , 0x7 , 0x3f , 0x39 , 0x30 , 0x1e , 0xa , 0x3c , 0xd , 0x2b , 0x38 , 0x12 , 0x11 , 0x42 , 0x28 , 0x2e , 0x0 , 0xb , 0x26 , 0x14 , 0x32 , 0x24 , 0x1c , 0x2f , 0x5 , 0x35 , 0x40 , 0x16 , 0xe , 0x19 , 0x21 };
    for(int k=0;k<0x29a-1;k++){
        int key_pos=0;
        for(int i=0;i<0x21;i++){
            v3 = i + rand() % (33 - i);
            int tmp = (rand() % (0x10002 - (0x120LL * v3 + 8))) + 0x607620;
            swap(first_turn,i,v3);
            start_in_key[i]=key_pos;
            key_pos+=strlen(first_turn[i]);
        }
        printf("\"");
        for(int i=0;i<0x21;i++){
            printf("%s",first_turn[i],start_in_key[i]);
        }
        printf("\",\n");
        for ( int i = 0; i< 0x21uLL; ++i ){
            int v4 = i + rand() % (33 - i);
            swap(first_turn,i,v4);
            swap_key(start_in_key,i,v4);

        }

        // for(int i=0;i<0x21;i++){
        //    printf("%s %d\n",first_turn[i],start_in_key[i]);
        // }

    }
    return 0;
}

通过这个程序就能计算出666个key值,然后挨个和加密数据异或便得到最终的结果
完整的python脚本在这里:go.py

得到结果:

mag!iC_mUshr00ms_maY_h4ve_g!ven_uS_Santa_ClaUs@flare-on.com