flag generatorの人力デコンパイルができなかったので

InterKosenCTFに参加しました.

冒頭になりますが


 

 

 

 

こんなに悔しいと思ったことはなかった
 

 

 

 

こんなに無力さを感じたのは久しぶりだった

 

 

 

 

 

 

 

...まあそれはいいとして,flag generatorをたぶんちょっと変わったやり方でやった(やっちまった)のではないかと思ったので載せておきます.

はじめに,flag generatorはおそらくこんな感じのプログラムだと思います.

#include <stdio.h>
#include <time.h>

int s;

void r()
{
    int tmp = s;
    tmp *= 0x41C64E6D;
    tmp += 0x3039;
    tmp = tmp & 0x7FFFFFFF;

    s = tmp;
};

int main()
{
    //from var_30 to var_c
    int constant1[10] = {
        0x608F5935,
        0x57506491,
        0x27365557,
        0x54E3DEA1,
        0x755A4ED5,
        0x17F42EB7,
        0x4A4F9059,
        0x1A08E827,
        0xD9D391F,
        0x59E533AA,
    };

    // var_38
    int constant2 = 0x25DC167E;
    //var_34
    int loopend = 0xA;
    //var_40
    int counter = 0;
    //var_3c
    int flag = 0;
    //var_44
    int calc_t = 0;

    int t;

    while (1)
    {
        if (flag == 0)
        {
            t = time(NULL);
            s = t;
        }

        r();

        calc_t = s;

        //t = 1509961785でフラグが立ちます
        if (calc_t == constant2)
        {
            flag = 1;
        }

        if (flag != 0)
        {
            calc_t = calc_t ^ constant1[counter];
            
            printf("%.4s", (char*)(&calc_t));
            
            counter += 1;
            if (counter == loopend)
            {
                break;
            }
        }
        sleep(1);
    }

    return 0;
}

要は,フラグが立っていればまずUNIXタイムを取得して,そのUNIXタイムに対しr()関数で計算をして,その結果が0x25dc167eと等しければフラグを立て,フラグが立っていれば定数配列のループカウンタ番目の数値と先ほどのr()の計算結果をxorして表示してからループカウンタをインクリメント,最後にフラグにかかわらず1秒間スリープします.以上の無限ループです.

当然時間的に待ってられないので,時間の部分をwhileループごとのインクリメントに変えます.

#include <stdio.h>
#include <time.h>

int s;

void r()
{
    int tmp = s;
    tmp *= 0x41C64E6D;
    tmp += 0x3039;
    tmp = tmp & 0x7FFFFFFF;

    s = tmp;
};

int main()
{
    //from var_30 to var_c
    int constant1[10] = {
        0x608F5935,
        0x57506491,
        0x27365557,
        0x54E3DEA1,
        0x755A4ED5,
        0x17F42EB7,
        0x4A4F9059,
        0x1A08E827,
        0xD9D391F,
        0x59E533AA,
    };

    // var_38
    int constant2 = 0x25DC167E;
    //var_34
    int loopend = 0xA;
    //var_40
    int counter = 0;
    //var_3c
    int flag = 0;
    //var_44
    int calc_t = 0;

    int t = 1509961780;

    while (1)
    {
        if (flag == 0)
        {
            s = t;
        }

        r();

        calc_t = s;

        //t = 1509961785でフラグが立ちます
        if (calc_t == constant2)
        {
            flag = 1;
        }

        if (flag != 0)
        {
            calc_t = calc_t ^ constant1[counter];
            
            printf("%.4s", (char*)(&calc_t));
            
            counter += 1;
            if (counter == loopend)
            {
                break;
            }
        }
        t += 1;
    }

    return 0;
}

これでキレイにフラグが出ます.
f:id:verliezer93764:20190121002209p:plain

しかし,このプログラムは実はctf終了後に書いたもので,競技中には実行ファイルはなぜか消えるわフラグは出ないわで,結局人力デコンパイルは諦めてしました.

このままでは埒が明かないのでELFを改造してみることを思い立ち以下のことを行いました.

1."call _time"を"mov eax, 0x5a003039"にする

これで無限ループ中の1回目のループでフラグ変数が立ち,1秒ごとにフラグの一部が表示されるようになります.なお,フラグ変数が立つとこの部分は実行されないのでプログラムにこの変更による悪影響はありません.
movの機械語もわからなくて困ってましたが,のちの"mov eax, 0"の機械語が"b8 00 00 00 00"で表されていることを参考に該当部分をIDAで探し,バイナリエディタ上でアドレスが0x00001205から5バイトを"e8 46 fe ff ff"から"b8 39 30 00 5a"に変更しました.

変更前が次の図
f:id:verliezer93764:20190121003209p:plain
変更後が次の図
f:id:verliezer93764:20190121003435p:plain

2."call _sleep"をNOPで埋める

これはオプションで,ただ数秒すら待たないようになるだけです.
該当部分をIDAで探し,バイナリエディタ上でアドレスが0x0000126cから5バイトを"e8 ef fd ff ff"から"90 90 90 90 90"に変更しました.

変更前が次の図
f:id:verliezer93764:20190121003653p:plain
変更後が次の図
f:id:verliezer93764:20190121003748p:plain

これらを行った結果,フラグは一部おかしいですが表示されました.最後の?が消えていたので,何度もKOSENCTF{IS_THIS_REALLY_A_REVERSING}で提出してWrongって言われて「は?!?!?!?」ってなりました()
f:id:verliezer93764:20190121005827p:plain

実は'?'の後に'\b'があるらしく,それで'?'が消えていたとのことですが,「さては(何らかの理由で)'?'が必要なのでは」と思って何とかcorrectになりました.でも何で上記のプログラムではうまくいったんでしょうかね....