以下の内容はhttps://hiikunz.hatenablog.com/entry/Daily_AlpacaHack_20251215より取得しました。


Flag Printer 2100(Daily AlpacaHack 2025/12/15) 公式WriteUp

はじめに

この記事では、私が 2025 年 12 月 15 日に Daily AlpacaHack で出題した Flag Printer 2100 の解説をします。問題を先に解きたい方は こちら からどうぞ。

問題文

75 年後にフラグを出力するプログラムを作ったので、2100 年まで待ってね!

添付ファイル: print_flag (ELF ファイル)

解説

まず、与えられたファイルを ghidra などで静的解析します。main 関数を見ると、以下のようなコードが見えます。

int main() {
    printf("I'll sleep for 75 years...\n");
    sleep(0x8d12cea0); // Sleep for 75 years
    show_flag();
    return 0;
}

このコードは、75 年間スリープした後に show_flag 関数を呼び出すというものです。ということで、問題文の通りに 75 年間待つことで解くことができそうですが、現実的ではありません。

show_flag 関数を見てみると、とても静的に動作を解析したくならない見た目をしています。そこで、動的解析を行うことを考えます。以下にいくつかの方針を示します。

1. パッチを当てる

バイナリエディタを用いて機械語を直接書き換えることを考えます。

まず、main 関数の objdump の結果を示します。

00000000000019d0 <main>:
    19d0:    f3 0f 1e fa              endbr64
    19d4:    55                       push   %rbp
    19d5:    48 89 e5                 mov    %rsp,%rbp
    19d8:    48 8d 05 31 06 00 00     lea    0x631(%rip),%rax        # 2010 <_IO_stdin_used+0x10>
    19df:    48 89 c7                 mov    %rax,%rdi
    19e2:    e8 a9 f6 ff ff           call   1090 <puts@plt>
    19e7:    bf a0 ce 12 8d           mov    $0x8d12cea0,%edi
    19ec:    e8 bf f6 ff ff           call   10b0 <sleep@plt>
    19f1:    b8 00 00 00 00           mov    $0x0,%eax
    19f6:    e8 b5 f7 ff ff           call   11b0 <show_flag>
    19fb:    b8 00 00 00 00           mov    $0x0,%eax
    1a00:    5d                       pop    %rbp
    1a01:    c3                       ret

ここにパッチを当てます。ここでは 2 通りの方法を示します。

まず、sleep に渡す引数を 0 にしてしまう方法です。

    19e7:    bf a0 ce 12 8d           mov    $0x8d12cea0,%edi

    19e7:    bf 00 00 00 00           mov    $0x0,%edi

に変えることで sleep する時間が 0 秒になります。

パッチ例 1

もう 1 つは、sleep を呼び出している箇所を nop 命令で置き換える方法です。

    19ec:    e8 bf f6 ff ff           call   10b0 <sleep@plt>

    19ec:    90                       nop
    19ed:    90                       nop
    19ee:    90                       nop
    19ef:    90                       nop
    19f0:    90                       nop

に変えることで、そもそも sleep が呼び出されなくなります。

パッチ例 2

2. デバッガーを利用する

デバッガーを用いてもこの問題を解くことができます。ここでは gdb を利用する解き方を示します。

もし gdb が入っていない場合

sudo apt install gdb

で導入できます。

まずは

gdb ./print_flag

gdb を起動しましょう。

起動したら

run

または

r

で一度プログラムを実行し、途中で Ctrl+C で停止して、

disassemble main

または

disas main

を実行して main 関数のアドレスを確認してみましょう。

   0x00005555555559d0 <+0>:     endbr64
   0x00005555555559d4 <+4>:     push   rbp
   0x00005555555559d5 <+5>:     mov    rbp,rsp
   0x00005555555559d8 <+8>:     lea    rax,[rip+0x631]        # 0x555555556010
   0x00005555555559df <+15>:    mov    rdi,rax
   0x00005555555559e2 <+18>:    call   0x555555555090 <puts@plt>
   0x00005555555559e7 <+23>:    mov    edi,0x8d12cea0
   0x00005555555559ec <+28>:    call   0x5555555550b0 <sleep@plt>
   0x00005555555559f1 <+33>:    mov    eax,0x0
   0x00005555555559f6 <+38>:    call   0x5555555551b0 <show_flag>
   0x00005555555559fb <+43>:    mov    eax,0x0
   0x0000555555555a00 <+48>:    pop    rbp
   0x0000555555555a01 <+49>:    ret
break *0x00005555555559ec

または

b *0x00005555555559ec

を実行して sleep 関数を呼ぶ地点でブレークポイントを設定した上で、再実行してみましょう。

ブレークポイントで止まったら、

info registers

または

i registers

レジスタの状態を見ることができます。sleep 関数を呼ぶときの第一引数は rdi レジスタで渡されるので、

set $rdi=0

で第一引数を 0 に変更することで、sleep する時間を 0 秒できます。

continue

または

c

でプログラムの実行を再開すると、フラグが得られます。


あるいは、ブレークポイントから

   0x00005555555559f6 <+38>:    call   0x5555555551b0 <show_flag>

に処理を飛ばすことでもこの問題を解くことができます。

jump *0x00005555555559f6

または

j *0x00005555555559f6

を実行すればよいです。

3. その他の方法

他にも、LD_PRELOAD を使って sleep 関数をフックする方法や、show_flag を頑張って解析する方法など、さまざまな方法が考えられます。

まとめ

この問題を静的解析で解くのは大変ですが、動的解析を行うことで楽に解くことができました。

flag: Alpaca{G00d_Morning_AlpacaH4ck!}




以上の内容はhttps://hiikunz.hatenablog.com/entry/Daily_AlpacaHack_20251215より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14