Fall Bozos: Ultimate KosenCTF
The timer is of integer type.
3Dゲームのexeファイルを渡された
10*10のタイルを越えて対岸に渡ればゴールらしい
だが、体感8割くらいの確立で踏んだタイルが崩落するので普通にゴールするのはつらそう
ゲーマーの威信にかけて根性でクリアしたら「もっとはやくクリアしやがれ」と言われた
仕方がないのでGhidraで解析することにする
Defined StringsにHSPという文字が散見されることからHSPで書かれたプログラムであると推測できる
「HSP exe 逆コンパイル」でggったらとてもよさそうなものが見つかった(https://github.com/YSRKEN/HSP-Decompiler)
投げてみた
#runtime "hsp3gp"
#struct modflag var prm_1
#regcmd "", ""
goto *label_1
#deffunc __init modinit modflag, str prm_3, str prm_4, str prm_5
var_0 = ginfo(16)
var_1 = ginfo(17)
var_2 = ginfo(18)
sdim var_3, 4
sdim var_4, 1
sdim var_5, 4
// /*09 20 004E*/ 6384761
var_6 = ginfo(17)
var_2 = ginfo(18)
lpoke var_3, 0, ginfo(18) * 16777216 + ginfo(16) * 65536 + ginfo(17) * 256 + 112
poke var_4, 0, ginfo(16)
// /*09 20 004E*/ 6775149
lpoke var_5, 0, 1694498816 + ginfo(18) * 65536 + ginfo(17) * 256 + ginfo(16)
// /*09 20 004E*/ 4412486
var_7 = ginfo(16)
var_8 = ginfo(17)
var_9 = ginfo(18)
color var_0, var_1, var_2
mes strf("KosenCTF{%s_%s_%s_%s_%s_%s_%c%c%c}", prm_3, var_3, var_4, var_5, prm_4, prm_5, var_7, var_8, var_9)
// /*09 20 004E*/ 14593470
return
*label_1
setreq 50, 0
title "Tamane Tip Toe"
randomize
// /*09 20 0060*/
setcls 1, 15761568
// /*09 20 00B5*/ 1048579 1 1 1
// /*09 20 00B3*/ 1048579 0.5 0.5 0.5
celload "res/text_eliminated.png", 4
celload "res/text_qualified.png", 5
buffer 1, ginfo(26), ginfo(27), 32 + 64
var_10 = 1
var_11 = 2
var_12 = dirinfo(4)
var_12 = strtrim(var_12, 0, 32)
if ( var_12 == "debug" ) {
var_13 = 1
dialog "argv[1]='debug' : debug mode activated", 0, "Developer Mode"
}
else {
var_13 = 0
}
var_14 = 16.0
var_15 = 0.0
var_16 = -20.0
var_17 = 0.0
var_18 = 0.0
var_19 = 0.0
var_20 = var_17
var_21 = 12.0
var_22 = 0
var_23 = 0
var_24 = 0
var_25 = 0
gosub *label_6
var_26 = 0
*label_2
gosub *label_5
gosub *label_3
gsel 1
redraw 0
gosub *label_4
// /*09 20 0061*/
redraw 1
gsel 0
redraw 0
gmode 0
pos 0, 0
celput 1
if ( var_13 ) {
pos 16, 16
color 0, 0, 0
mes strf("(px, py, pz) = (%f, %f, %f)", var_15, var_27, var_16)
mes strf("(cx, cy, cz) = (%f, %f, %f)", var_28, 10.0, var_29)
mes strf("(dx, dz) = (%f, %f)", var_18, var_30)
mes strf("animation = %d", var_22)
mes strf("time = %f", var_26)
}
if ( var_25 == 1 ) {
pos ginfo(26) / 2 - 268, ginfo(27) / 2 - 45
gmode 2
gcopy 4, 0, 0, 537, 90
}
else {
if ( var_25 == 2 ) {
pos ginfo(26) / 2 - 240, ginfo(27) / 2 - 45
gmode 2
gcopy 5, 0, 0, 480, 90
pos ginfo(26) / 2 - 160, ginfo(27) / 2 + 60
color 80, 240, 80
if ( var_26 < 100 ) {
newmod var_31, modflag, "Let's", "other", "than"
delmod var_31
}
else {
mes "Tips: Finish more quickly to get the flag!"
}
}
}
redraw 1
await 1000 / 60
if ( var_25 == 0 ) {
var_26++
}
goto *label_2
*label_3
// /*09 20 00D9*/ var_32 var_33 3.0
if ( var_32 != (-1) ) {
repeat 10
var_34 = cnt
repeat 10
if ( var_35(var_34, cnt) == var_32 ) {
if ( var_36(var_34, cnt) == 0 ) {
// /*09 20 007D*/ var_35 ( var_34 cnt ) 1.0 0.0
}
else {
// /*09 20 0070*/ var_35 ( var_34 cnt )
var_35(var_34, cnt) = -1
// /*09 20 0078*/ var_37 4.8 15790208
// /*09 20 00B2*/ var_37 1.0 0.1 1.0
// /*09 20 00DA*/ var_37 var_10
// /*09 20 00B0*/ var_37 -22.0 cnt 5 * + -0.25 -12.0 var_34 5 * +
}
}
loop
loop
}
return
*label_4
// /*09 20 0080*/ var_33 var_38 var_27 var_39
var_18 = var_38 - var_15
var_30 = var_39 - var_16
var_15 = var_38
var_16 = var_39
// /*09 20 00B0*/ var_40 var_15 var_27 1.0 - var_16
// /*09 20 00B9*/ var_40 0 var_17
// /*09 20 00B9*/ var_33 0 var_17
if ( var_23 == 1 & var_27 > 1.2 ) {
var_23 = 2
}
if ( var_23 == 2 & var_27 < 1.2 ) {
var_23 = 0
// /*09 20 00FA*/ var_40 "stop" 1
var_22 = 0
}
if ( var_24 == 1 ) {
if ( var_23 == 0 ) {
// /*09 20 00F2*/ var_33 0 var_21 sin ( var_17 ) * 0.0 var_21 cos ( var_17 ) *
}
var_41 = var_18 * var_18 + var_30 * var_30
// /*09 20 00F2*/ var_33 0 var_41 sin ( var_17 3.14159265358979 + ) * 0.0 var_41 cos ( var_17 3.14159265358979 + ) *
}
else {
if ( var_23 == 0 ) {
// /*09 20 00F2*/ var_33 0 0.0 -10.0 0.0
}
}
if ( var_27 < (-5.0) & var_25 == 0 ) {
// /*09 20 00F0*/ var_33 0 0
// /*09 20 00F3*/ var_40 0.0
var_25 = 1
}
if ( var_16 > 35.0 & var_25 == 0 ) {
// /*09 20 00F0*/ var_33 0 0
// /*09 20 00F3*/ var_40 0.0
var_25 = 2
}
var_28 = var_15 - var_14 * sin(var_20)
var_29 = var_16 - var_14 * cos(var_20)
// /*09 20 00B0*/ 1048578 var_28 8 var_29
// /*09 20 007C*/ 1048578 var_15 3.0 var_27 + var_16
return
*label_5
getkey var_42, 87
getkey var_43, 65
getkey var_44, 68
if ( var_23 == 0 ) {
if ( var_43 ) {
var_17 += 0.04
}
else {
if ( var_44 ) {
var_17 -= 0.04
}
}
if ( var_42 ) {
if ( var_22 != 1 ) {
// /*09 20 00FA*/ var_40 "run" 1
var_22 = 1
}
var_24 = 1
}
else {
var_24 = 0
if ( var_22 == 1 ) {
if ( var_43 == 0 & var_44 == 0 ) {
// /*09 20 00FA*/ var_40 "stop" 1
var_22 = 0
}
else {
// /*09 20 00FA*/ var_40 "walk" 1
var_22 = 2
}
}
else {
if ( var_43 == 0 & var_44 == 0 ) {
if ( var_22 != 0 ) {
// /*09 20 00FA*/ var_40 "stop" 1
var_22 = 0
}
}
else {
if ( var_22 == 0 ) {
// /*09 20 00FA*/ var_40 "walk" 1
var_22 = 2
}
}
}
}
}
stick var_45, 16
if ( var_45 & 16 ) {
if ( var_23 == 0 ) {
// /*09 20 00F2*/ var_33 1 0 6 0
// /*09 20 00FA*/ var_40 "jump" 1
var_23 = 1
}
}
return
*label_6
// /*09 20 0077*/ var_46 50 10 4227312
// /*09 20 00B0*/ var_46 0.0 0.0 -20.0
// /*09 20 007D*/ var_46 0 0.8
// /*09 20 0077*/ var_47 50 10 4227312
// /*09 20 00B0*/ var_47 0.0 0.0 40.0
// /*09 20 007D*/ var_47 0 0.8
// /*09 20 0075*/ var_40 "res/tamane"
// /*09 20 00B2*/ var_40 0.01 0.01 0.01
// /*09 20 00F7*/ var_40 "stop" 1000 3000
// /*09 20 00F7*/ var_40 "run" 5000 5700
// /*09 20 00F7*/ var_40 "walk" 4000 4800
// /*09 20 00F7*/ var_40 "jump" 140 140
// /*09 20 00FA*/ var_40 "stop" 1
// /*09 20 0078*/ var_33 2.0 16711680
// /*09 20 00B2*/ var_33 0.8 1.0 0.8
// /*09 20 00B0*/ var_33 var_15 2.0 var_16
// /*09 20 00DA*/ var_33 var_11 var_10
// /*09 20 007D*/ var_33 1 0.8
// /*09 20 00F0*/ var_33 5 0.0 -9.8 0.0
if ( var_13 ) {
// /*09 20 00F3*/ var_33 50.0
}
else {
// /*09 20 00F3*/ var_33 0.0
}
dim var_35, 10, 10
dim var_48, 10, 10
dim var_36, 10, 10
repeat 10
var_34 = cnt
repeat 10
// /*09 20 0078*/ var_35 ( var_34 cnt ) 4.8 14708784 rnd ( 16 ) 65536 * + rnd ( 16 ) 256 * + rnd ( 16 ) +
// /*09 20 00B2*/ var_35 ( var_34 cnt ) 1.0 0.1 1.0
// /*09 20 00DA*/ var_35 ( var_34 cnt ) var_10
// /*09 20 00B0*/ var_35 ( var_34 cnt ) -22.0 cnt 5 * + -0.25 -12.0 var_34 5 * +
if ( rnd(3) == 0 ) {
var_36(var_34, cnt) = 1
// /*09 20 0077*/ var_48 ( var_34 cnt ) 4.8 4.8 65280
// /*09 20 00B0*/ var_48 ( var_34 cnt ) -22.0 cnt 5 * + 0.0 -12.0 var_34 5 * +
// /*09 20 007D*/ var_48 ( var_34 cnt ) 0 0.8
if ( var_13 ) {
// /*09 20 00F3*/ var_48 ( var_34 cnt ) 50.0
}
else {
// /*09 20 00F3*/ var_48 ( var_34 cnt ) 0.0
}
}
loop
loop
return
上記ツールから吐き出されたhspファイルを解析する
42-44行目を見るとコマンドラインにdebug
を渡して実行するとデバッグモードに入れると分かる
デバッグモードでは座標や経過時間を確認することができる
ここで重要なのが経過時間が分かるようになるという点
経過時間の値が分かれば、デバッガでその値で検索することによって経過時間のアドレスを特定できる
そして、そのアドレスの値を書き換えることで経過時間を任意の値に設定し直せる
つまり、根性でゴール→経過時間書き換えとすればflagが得られる
経過時間のアドレスは 現在時刻よりちょっと高い値(操作時間による遅延のフォロー)以下で検索→現在時刻がさっき検索した値以上になったら条件を以上にして再検索→増加を複数回押す とすると候補が3つくらいに絞れるので、それらのアドレスの値を片っ端から書き換えてみてtimeの変化を見れば特定できる
あとは根性でゴールして特定したアドレスを0にすればflag
KosenCTF{Let's_play_a_game_other_than_CTF}
ちなみにゴールしたかどうかの値が leftTime+0x54の位置に入っており、そこの値を2にすればゴールしたのと実質的に一緒になるのでわざわざ根性でゴールする必要はなかったりする