2020 KCTF秋季賽 | 第五題設計及解題思路

Editor發表於2020-11-30

2020 KCTF秋季賽 | 第五題設計及解題思路


2020 KCTF正在如火如荼的進行中!已有超過5萬人圍觀!第五題《緊急救援》歷時4天,最終以 2 支戰隊破解的成績落下帷幕。


在賽題即將結束的最後一天凌晨,k1ee 戰隊逆轉直上,在凌晨2點42分攻擊成功!併成功升至第二名的寶座,恭喜!ReZero戰隊隨後也在7點左右提交答案,成為第二支挑戰成功的戰隊,守住了第一的位置。


2020 KCTF秋季賽 | 第五題設計及解題思路


賽場如戰場,各位選手不輕言棄,更是體現了高手過招的魅力!刀光劍影,是比拼更是成就,一招一式盡顯各位英雄本色!


那麼這道讓眾多團隊都倍感遺憾的賽題,作者究竟都使用了哪些技術與tricks呢?讓我們一起來看看吧!



一. 題目簡介


逃離魔爪的你拖著疲憊的身心打算先找個地方安頓下來,瞭解目前的情況。


此時,腦海中第一個浮現的就是“他”——你的黑客摯友肖恩。肖恩是你任職於國家網路安全域性時交到的唯一的朋友,跟你一樣也是國安局的王牌。現在仍在職的他自然是收到了“破曉”的電子邀請函的。


但他不像你,自幼父母在事故中喪生,孤家寡人,無牽無掛。正是因為自己的女兒患先天疾病,被Norns判定為無資格登上方舟者,肖恩才毅然選擇和家人們一起留在地球。


其實在“破曉”上檢測到來自這座城市的異常訊號時,你就猜測可能是肖恩的手筆了。


驅車趕往肖恩家,摁響門鈴,迎接你的是肖恩的妻子。看到你的瞬間她瞪大了眼睛,驚訝、疑惑多種複雜的情緒快速變換著。你越過她朝家裡掃視了一圈,心中的石頭總算落下了,他們一家三口都平安無事,而且看起來都很正常。


但進了家門,你卻越發覺得平靜之下,暗藏波瀾。肖恩妻女臉上的笑容總是很僵硬,平時對肖恩愛意滿滿的眼神如今卻流露著不著痕跡的恐懼……肖恩卻不以為然,微笑著夾起肉放在你的碗裡。


飯桌上那說不出是溫馨還是詭異的氣氛讓可口的飯菜都變得有些難以下嚥。你抬頭對上肖恩的視線,與剛才相比沒有絲毫變化,就連嘴上揚起的弧度也如此標準而刻板。


你起了一身雞皮疙瘩,再望向肖恩的妻子,她沒有出聲但好像在暗示你什麼。你努力辨認她的口型,似乎是?


“快……逃……”


你站起來的瞬間,肖恩就朝你迎頭一擊,試圖將你制服。你抄起刀與他對峙,發現他的傷口流出了仿生血液。


控制住他後,肖恩妻子告訴你,地球沒有毀滅,但不知為何仿生人突然暴動,地球上大部分人類都被他們控制,而真正的肖恩也被他們抓走。


趕快利用仿生人肖恩的晶片尋找線索,定位肖恩的地理位置,解救他!



二. 出題團隊簡介


2020 KCTF秋季賽 | 第五題設計及解題思路



三. 看雪專家點評

點評由 HHHso 提供


本題防守方採用了多層VM的設計思路,比賽中VM常見,多層VM罕見,讓攻擊者欲罷不能。


當攻擊者們在尋找快速方案如手解等方式撥開第一層VM後,發現後面還是VM,套娃感凸現,一望無際,定力稍有不慎,可能就會被嚇住。這時候需要攻擊者使用各種看家本領透視VM業務邏輯,層層擊破,得到可閱讀的程式碼。


當VM面紗都揭開時,高層VM對底層VM的執行時修改以及暗藏邏輯的觸發聯動機制也需要一一解開,才能聯合起來得到完整的驗證邏輯進行解題。高層VM對支撐其運作的低層VM修改和聯動,除了套娃感,也略帶虛擬逃逸感。總體設計非常優秀,相信各位攻擊者都有所得。



四. 設計思路

設計思路由作者 ccfer 提供


題目型別:Window平臺CrackMe

系統需求:WIN10/64

成功提示:Success!

題目答案: 

AECCC0DE0C3256F75E37A6BFA227A2ED3D54AC964B43544632303230466C6167826B49EB0A305A72C2E92C18A0901280F47791BAE00932B0


設計說明

遊戲原型是熄燈拼圖,通過若干次的操作,每次切換某方塊及其上下左右四個相鄰方塊的開關狀態,直到全部狀態完成翻轉。


首先是個10*10的熄燈拼圖,這規模小能直接搜到答案,需要最少44次操作,實現完全翻轉,得到的解(開關操作位置圖)是:


2020 KCTF秋季賽 | 第五題設計及解題思路


防止直接搜到答案,後面又加了一個40*40的,需要解線性方程組(用sage很快就跑出結果)。


詳情參考:

hxxps://mathworld.wolfram.com/LightsOutPuzzle.html

 

然後這個10*10的解7位一組轉成15個位元組,每個位元組最高位補充了校驗位,最後再補充1個位元組,變成16位元組的key,用k表示。


為了壓縮輸入長度,40*40的解因為也是左右上下及對角線對稱,可以用一個八分之一的區域性三角形經三次映象得到40*40的矩陣,輸入只需要(1+19)*19/2=190位,8位一組轉成24個位元組用e表示。


整個完整key是56位元組,可以表示為:

aaaakkkkkkkkkkkkkkkkbbbbccccddddeeeeeeeeeeeeeeeeeeeeeeee


其中aaaa、bbbb、cccc、dddd是固定常數,a部分的DWORD經過8輪Koopman多項式的crc運算。


k、b、c、d部分的28位元組組成14個WORD,用ECC做一次點乘運算,ECC引數:p=65011,a=65008,b=6,G=(1,2)。


這個微型ECC直接窮舉即可求解,每個點會有兩個解,收集所有最高位排除多解


難度提升

演算法用C語言實現,然後用一個簡單的C編譯器編譯成opcode,再解釋執行,相當於一個簡單的虛擬機器,感覺這個虛擬機器也是相當簡單了,於是把直譯器也編譯一遍,做一次巢狀的解釋執行,大概意思是:從前有座山,山上有座廟,廟裡有個老和尚,老和尚在給小和尚講故事,故事講的是......


解題思路

虛擬機器比較簡單,先把第一層opcode還原出來,發現邏輯竟然等同於直譯器自身,只是opcode定義有些亂序,然後再還原出第二層和第三層,最後第四層裡就只剩下簡單的驗證邏輯瞭然後ECC的p只有16位,直接窮舉即可求解。


LightsOut求解直接用sage也是迅速得出結果。


crc暴力窮舉會慢點,不過4位元組得相當於可逆,有快速破解方法。



五. 解題思路


設計思路由作者 k1ee 提供


緊急救援


我真的沒做過虛擬機器題,這題套了4層虛擬機器,屬實給力,不能再用以前的手打虛擬機器方法了,必須程式化。首先拖入IDA分析。

 

image-20201128024730937

 

輸入一段Hex Text,轉為Hex Bytes。

 

image-20201128024813802

 

建立緩衝區,複製虛擬機器指令到如圖位置。隨後按以下結構體構造了虛擬機器的引數


struct vm_sub
{
    int param1; //6, 6, 6, 3
    int param2; //ins1, ins2, ins3, input_hex
    char* vm_ins;
    int size;
    int idk_0;
    int id;
    int idk7;
    int idk8;
};
 
struct vm_fin
{
    unsigned char* input_hex;
    int* len_buf;
    vm_sub* vmsub;
};
 
struct vm_context
{
    vm_sub subs[4];
    vm_fin fin;
};


隨後傳入第一個虛擬機器上下文引數(vm_sub)開始執行虛擬機器,並由結果進行輸出。

 

image-20201128025053027

 

進入虛擬機器函式,廢話和彎路我就不多說了,直接分析可知

 

image-20201128025140702

 

這是典型的壓棧,再看後續指令

 

image-20201128025202263

 

基本都是通過堆疊進行計算。經過兩天的彎路後,我最終決定通過KeyStone還原各層虛擬機器的原始碼。由於1,2,3層虛擬機器的指令僅僅是替換而已,因此這裡只分析第1層。(Butterfly為第0層,三個Buffer分別是1、2、3層,最後一層是關鍵程式碼)

 

只需要模仿通常靜態分析手段掃過去就行了,然後按照對應OpCode生成彙編,然後再進行編譯,貼上原始碼


#include <keystone/keystone.h>
...
 
void disassembly_vm1(vm_sub* ctx)
{
    char* eip = ctx->vm_ins;
    char* esp = eip + 2 * ctx->size;
 
    ks_engine* ks;
    ks_err err;
 
    err = ks_open(KS_ARCH_X86, KS_MODE_32, &ks);
    if (err != KS_ERR_OK)
    {
        cout << "Keystone open error." << endl;
        return;
    }
 
    ostringstream dasm = ostringstream();
 
    dasm << "push    6;" << endl;
    dasm << "push    0x20000;" << endl;
    dasm << "call    vmins_0;" << endl;
    dasm << "jmp    vmins_ret;" << endl;
 
    while (eip < ctx->vm_ins + 0x792)
    {
        int vm_offset = eip - ctx->vm_ins;
        dasm << "vmins_" << vm_offset << ":" << endl;
 
        int ins = *eip++;
 
        switch (ins)
        {
        case 17:
        {
            dasm << "push    ebx;" << endl;
            break;
        }
        case 1:
        {
            uint8_t off = (uint8_t)*eip++;
            dasm << "xor    eax, eax;" << endl;
            dasm << "mov    al, " << (int)off << ";" << endl;
            dasm << "lea    ebx, [ebp+eax*4-400h];" << endl;
            break;
        }
        case 13:
        {
            dasm << "mov    ebx, [ebx];" << endl;
            break;
        }
        case 3:
        {
            ecx = (uint8_t)*eip++;
            dasm << "mov    ebx, " << (int)ecx << ";" << endl;
            break;
        }
        case 8:
        {
            uint32_t off = *(uint32_t*)eip;
            dasm << "test    ebx, ebx;" << endl;
            dasm << "jz        vmins_" << (int)(vm_offset + 1 + off) << ";" << endl;
            dasm << "jmp    vmins_" << (int)(vm_offset + 1 + 4) << ";" << endl;
            eip += 4;
            break;
        }
        case 21:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "cmp    ecx, ebx;" << endl;
            dasm << "jnz    vmins_" << vm_offset << "set0;" << endl;
            dasm << "mov    ebx, 1;" << endl;
            dasm << "jmp    vmins_" << vm_offset + 1 << ";" << endl;
            dasm << "vmins_" << vm_offset << "set0:" << endl;
            dasm << "mov    ebx, 0;" << endl;
            break;
        }
        case 15:
        {
            dasm << "pop    edx;" << endl;
            dasm << "mov    [edx], ebx;" << endl;
            break;
        }
        case 6:
        {
            uint32_t off = *(uint32_t*)eip;
            //In disassembly mode we do not jump, but skip this instruction.
            //eip += off;
 
            if (off != 4)
                dasm << "jmp    vmins_" << (int)(vm_offset + 1 + off) << ";" << endl;
 
            eip += 4;
            break;
        }
        case 29:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "add    ebx, ecx;" << endl;
            break;
        }
        case 30:
        {
            dasm << "pop    eax;" << endl;
            dasm << "sub    eax, ebx;" << endl;
            dasm << "mov    ebx, eax;" << endl;
            break;
        }
        case 14:
        {
            dasm << "xor    ecx, ecx;" << endl;
            dasm << "mov    cl, [ebx];" << endl;
            dasm << "mov    ebx, ecx;" << endl;
            break;
        }
        case 31:
        {
            dasm << "pop    edx;" << endl;
            dasm << "imul    ebx, edx;" << endl;
            break;
        }
        case 16:
        {
            dasm << "pop    eax;" << endl;
            dasm << "mov    [eax], bl;" << endl;
            dasm << "movsx    ebx, bl;" << endl;
            break;
        }
        case 33:
        {
            dasm << "pop    eax;" << endl;
            dasm << "xor    edx, edx;" << endl;
            dasm << "div    ebx;" << endl;
            dasm << "mov    ebx, edx;" << endl;
            break;
        }
        case 23:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "cmp    ecx, ebx;" << endl;
            dasm << "jnb    vmins_" << vm_offset << "set0;" << endl;
            dasm << "mov    ebx, 1;" << endl;
            dasm << "jmp    vmins_" << vm_offset + 1 << ";" << endl;
            dasm << "vmins_" << vm_offset << "set0:" << endl;
            dasm << "mov    ebx, 0;" << endl;
            break;
        }
        case 32:
        {
            dasm << "pop    eax;" << endl;
            dasm << "xor    edx, edx;" << endl;
            dasm << "div    ebx;" << endl;
            dasm << "mov    ebx, eax;" << endl;
            break;
        }
        case 24:
        {
            dasm << "pop    edx;" << endl;
            dasm << "cmp    edx, ebx;" << endl;
            dasm << "jbe    vmins_" << vm_offset << "set0;" << endl;
            dasm << "mov    ebx, 1;" << endl;
            dasm << "jmp    vmins_" << vm_offset + 1 << ";" << endl;
            dasm << "vmins_" << vm_offset << "set0:" << endl;
            dasm << "mov    ebx, 0;" << endl;
            break;
        }
        case 18:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "or        ebx, ecx;" << endl;
            break;
        }
        case 28:
        {
            dasm << "pop    eax;" << endl;
            dasm << "mov    ecx, ebx;" << endl;
            dasm << "shr    eax, cl;" << endl;
            dasm << "mov    ebx, eax;" << endl;
            break;
        }
        case 20:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "and    ebx, ecx;" << endl;
            break;
        }
        case 19:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "xor    ebx, ecx;" << endl;
            break;
        }
        case 27:
        {
            dasm << "pop    edx;" << endl;
            dasm << "mov    ecx, ebx;" << endl;
            dasm << "shl    edx, cl;" << endl;
            dasm << "mov    ebx, edx;" << endl;
            break;
        }
        case 22:
        {
            dasm << "pop    eax;" << endl;
            dasm << "cmp    eax, ebx;" << endl;
            dasm << "jz    vmins_" << vm_offset << "set0;" << endl;
            dasm << "mov    ebx, 1;" << endl;
            dasm << "jmp    vmins_" << vm_offset + 1 << ";" << endl;
            dasm << "vmins_" << vm_offset << "set0:" << endl;
            dasm << "mov    ebx, 0;" << endl;
            break;
        }
        case 26:
        {
            dasm << "pop    ecx;" << endl;
            dasm << "cmp    ecx, ebx;" << endl;
            dasm << "jb        vmins_" << vm_offset << "set0;" << endl;
            dasm << "mov    ebx, 1;" << endl;
            dasm << "jmp    vmins_" << vm_offset + 1 << ";" << endl;
            dasm << "vmins_" << vm_offset << "set0:" << endl;
            dasm << "mov    ebx, 0;" << endl;
            break;
        }
        case 0:
        {
            uint8_t off = (uint8_t)*eip++;
            //ecx = (uint32_t)&eax[4 * off]; 
 
            dasm << "xor    edx, edx;" << endl;
            dasm << "mov    dl, " << (int)off << ";" << endl;
            dasm << "lea    ebx, [ebp+edx*4];" << endl;
            break;
        }
        case 11:
        {
            uint32_t off = *(uint32_t*)eip;
            //esp += 4 * off;
 
            dasm << "mov    eax, " << (int)(off * 4) << ";" << endl;
            dasm << "add    esp, eax;" << endl;
 
            eip += 4;
            break;
        }
        case 4:
        {
            ecx = *(uint32_t*)eip;
            eip += 4;
 
            dasm << "mov    ebx, " << (int)ecx << ";" << endl;
            break;
        }
        case 40:
        {
            //We do not execute
            //char* buf = (char*)*((uint32_t*)esp + 2);
            //uint32_t size = *(uint32_t*)esp;
            //ecx = (uint32_t)buf;
            //memset(buf, esp[4], size + (size & 3));
            //eax = ebx;
 
            dasm << "mov    ecx, [esp+0];" << endl;
            dasm << "xor    eax, eax;" << endl;
            dasm << "mov    al, [esp+4];" << endl;
            dasm << "mov    edi, [esp+8];" << endl;
            dasm << "mov    ebx, edi;" << endl;
            dasm << "rep stosb;" << endl;
            break;
        }
        case 42:
        {
            //We do not execute
            //ecx = (uint32_t) * ((uint32_t*)esp + 2);
            //memcpy((void*)*((uint32_t*)esp + 2), (void*)*((uint32_t*)esp + 1), *((uint32_t*)esp));
            //eax = ebx;
 
            dasm << "mov    ecx, [esp+0];" << endl;
            dasm << "mov    edi, [esp+8];" << endl;
            dasm << "mov    esi, [esp+4];" << endl;
            dasm << "mov    ebx, edi;" << endl;
            dasm << "rep movsb;" << endl;
            break;
        }
        case 9:
        {
            uint32_t off = *(uint32_t*)eip;
 
            dasm << "test    ebx, ebx;" << endl;
            dasm << "jz        vmins_" << (int)(vm_offset + 1 + 4) << ";" << endl;
            dasm << "jmp    vmins_" << (int)(vm_offset + 1 + off) << ";" << endl;
 
            eip += 4;
            break;
        }
        case 2:
        {
            uint32_t off = *(uint32_t*)eip;
            //ecx = (uint32_t)&eax[4 * off];
            eip += 4;
 
            dasm << "mov    ecx, " << (int)off << ";" << endl;
            dasm << "lea    ebx, [ebp+ecx*4];" << endl;
            break;
        }
        case 7:
        {
            uint32_t off = *(uint32_t*)eip;
            //push(esp, (uint32_t)eip + 4);
            //In disassembly mode we do not jump, but skip this instruction.
            //eip += off;
 
            dasm << "call    vmins_" << (int)(vm_offset + 1 + off) << ";" << endl;
            dasm << "mov    ebx, eax;" << endl;
 
            eip += 4;
            break;
        }
        case 10:
        {
            uint32_t off = *(uint32_t*)eip;
 
            dasm << "push    ebp;" << endl;
            dasm << "mov    ebp, esp;" << endl;
            dasm << "sub    esp, " << off * 4 << ";" << endl;
 
            eip += 4;
            break;
        }
        case 12: // return
        {
            dasm << "mov    eax, ebx;" << endl;
            dasm << "mov    esp, ebp;" << endl;
            dasm << "pop    ebp;" << endl;
            dasm << "ret;" << endl;
            break;
        }
        case 43:
        {
            dasm << "mov    eax, [esp];" << endl;
            dasm << "ret;" << endl;
            goto finished;
        }
        default:
        {
            cout << "Error";
            break;
        }
        }
    }
 
finished:
    dasm << "vmins_ret:" << endl;
    dasm << "push    ebx;" << endl;
    dasm << "mov    eax, [esp];" << endl;
    dasm << "ret;" << endl;
 
    unsigned char* output;
    size_t outlen = 0;
    size_t outcnt = 0;
    string disasm = dasm.str();
 
    ofstream fout = ofstream("./disasm_vm1.txt", ios_base::ate);
    fout << disasm;
    fout.flush();
    fout.close();
 
    const char* code = disasm.c_str();
 
    if (ks_asm(ks, code, 0, &output, &outlen, &outcnt) != KS_ERR_OK)
    {
        ks_err err = ks_errno(ks);
        cout << err;
    }
 
    fout = ofstream("./disasm_vm1.bin", ios_base::ate | ios_base::binary);
    fout.write((const char*)output, outlen);
    fout.flush();
    fout.close();
 
    ks_free(output);
    ks_close(ks);
}


需要注意的是,除了第1層的40和42,以及後續層的這兩個位置的指令,其他各層都相同,因此分析後面的只需要改一下case就行了。額外,第2、3層的這兩個指令加了不少其他程式碼,但是我發現不對增加的程式碼進行增補也可以解題,後面細說。除此之外,還需要注意把lea esp的地方改為add/sub esp,不然IDA不認(非標準

 

分析完4層指令後,貼上關鍵的反編譯函式。

  • 第1層

    image-20201128030230859


  • 第2層

    image-20201128030303024

image-20201128030311730


  • 第3層

    image-20201128030334055

image-20201128030342522

 

最終分析目標函式(第4層)

 

image-20201128030418531

 

首先獲取了前面幾層的指令指標

 

image-20201128030457430

 

這裡其實呼叫了memcpy系列函式,不過被優化了,由於我偷懶,並沒有為每層更改memcpy,memset系列函式的實現,因此看到這個指令,就可以認為呼叫了Host的那個地方的函式,轉而看前面層的程式碼就可以了。在這裡,經過提取分析,得到這裡memcpy對前4位元組的CRC32


int vm3_memcpy(char* dst, char* src, int len)
{
    vm_fin* v18 = &ctx.fin;
    unsigned char * v17 = v18->input_hex;
    int* v16 = new int[10];
    v16[2] = 0xDEC0CCAE;
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    *(v16 + 2) = crc32(0xFFFFFFFF, v16 + 2);
    if (*(v16 + 2) == 0xDE05629C)
        return 1;
    return 0;
}


經過爆破,可以得出前4位元組為AE CC C0 DE


result = d54b1112 target = de05629c
result = 5ba49ea3 target = d54b1112
result = f16f3846 target = 5ba49ea3
result = 84b4f299 target = f16f3846
result = 3731ce56 target = 84b4f299
result = 74f3e321 target = 3731ce56
result = 20558f1 target = 74f3e321
result = dec0ccae target = 20558f1
result = f812fce7 target = dec0ccae


隨後分析下一個函式


 

image-20201128030913123

 

這裡修改了上層Host的指令,可以看出是修改了一些立即數(前後對照),因此在還原函式的時候稍加註意即可,對於第一個memset,提煉出關鍵校驗函式有:


int vm3_memset_1(char* dst, char val, int len)
{
    vm_fin* v18 = &ctx.fin;
    char* hex = (char *)v18->input_hex;
    int* v16 = v18->len_buf;
    int* v6 = v16 + 4;
    *v6 = sub_E21(hex); // equals D540
    v6 = v16 + 2;
    *v6 = sub_109A(hex + 4, (char*)v16 + 256);
    memset(v16 + 1024, 1, 100);
    sub_1517((char*)v16 + 256, (char*)v16 + 4096);
    return 0;
}


sub_E21完成了某種變換,可以通過爆破還原,並計算了一個值(D540)避免多解,隨後由於前4位元組已經計算出,帶偏移傳入sub_109A


unsigned int __cdecl sub_109A(char* a1, char* a2)
{
    int v3; // [esp+3ECh] [ebp-14h]
    char* v4; // [esp+3F0h] [ebp-10h]
    unsigned __int8 v5; // [esp+3F4h] [ebp-Ch]
    unsigned int v6; // [esp+3F8h] [ebp-8h]
    unsigned int v7; // [esp+3FCh] [ebp-4h]
 
    v4 = a2;
    v7 = 0;
    v3 = 0;
    while (v7 < 15)
    {
        v6 = 0;
        v5 = a1[v7];
        v3 <<= 1;
        v3 |= (unsigned int)v5 >> 7;
        while (v6 < 7)
        {
            *v4 = v5 & 1;
            v5 >>= 1;
            ++v4;
            ++v6;
        }
        ++v7;
    }
    return (((v3 << 8) + ((unsigned int)(unsigned __int8)a1[14] >> 2)) << 8) + (unsigned __int8)a1[15];
}


前14個位元組以及15位元組的低2位變成10*10矩陣,隨後初始化棋盤,使用sub_1517進行解密。

 

image-20201128031325050

 

完成的是根據輸入,從左上角依次訪問棋盤,並對訪問位置及其相鄰的元素進行異或,最終使得全1變為全0。這裡演算法不多說,可以去看文章。完成求解

 

image-20201128031526150

 

此時根據這裡的防止多解,完成前8個int的求解

 

image-20201128031629283

 

image-20201128022715253

 

image-20201128022652192


AE CC C0 DE 0C 32 56 F7 5E 37 A6 BF A2 27 A2 ED 3D 54 AC 96 4B 43 54 46 32 30 32 30 46 6C 61 67


最後分析最後6個int,和前面棋盤大同小異,192位元的前190位元以三角形的方式放入矩陣,並將三角形複製8次填滿矩陣,然後求解使得棋盤翻轉。


int vm3_memset_2(char* dst, char val, int len)
{
    vm_fin* v18 = &ctx.fin;
    char* v17 = (char *)v18->input_hex;
    int* v16 = v18->len_buf;
    int* v6 = v16 + 2;
    *v6 = sub_179A(v17 + 32, (char *)v16 + 64*4);
    if (*v6 == 2)
    {
        memset(v16 + 1024, 1, 1600);
        sub_2FB9((char*)v16 + 4*64, (char*)v16 + 4*1024);
    }
    return 0;
}


*v6 == 2指的是剩餘2位元(高2位),因此求解該矩陣


uint32_t limit = pow(2, 20) - 1;
for (uint32_t val = 0; val <= limit; ++val)
{
    for (int i = 0; i < 20; ++i)
    {
        uint8_t bit = (val >> i) & 0b1;
        mat[0][i] = bit;
        mat[0][39 - i] = bit;
    }
    memset(table, 1, sizeof(table));
    for (int i = 0; i < 40; ++i)
    {
        for (int k = 0; k < 40; ++k)
        {
            uint8_t bit = mat[i][k];
            table[i][k] ^= bit;
            if (i > 0)
                table[i - 1][k] ^= bit;
            if (i < 39)
                table[i + 1][k] ^= bit;
            if (k > 0)
                table[i][k - 1] ^= bit;
            if (k < 39)
                table[i][k + 1] ^= bit;
        }
        if (i != 39)
        {
            for (int k = 0; k < 40; ++k)
            {
                mat[i + 1][k] = table[i][k];
            }
        }
    }
 
    if (memcmp(table, truth, sizeof(truth)) == 0)
    {
        printf("Result\n");
        printf("arr = []\n");
        for (int i = 0; i < 40; ++i)
        {
            printf("arr.append([");
            for (int k = 0; k < 40; ++k)
            {
                printf("%d%s", mat[i][k], k == 39 ? "" : ", ");
            }
            printf("]\n");
        }
    }
}


最終完成求解




拿指令碼解出來


image-20201128032000347

 

因此最終Flag為

1

AECCC0DE0C3256F75E37A6BFA227A2ED3D54AC964B43544632303230466C6167826B49EB0A305A72C2E92C18A0901280F47791BAE00932B0


image-20201128032048350


相關文章