2020年12月3の21:30 - 12月4日21:30 で行われていた、Shakti CTF 2020の [Reversing] 分野のwriteupです。
※ まとめはこちら tech.kusuwada.com
Just-Run-It!
Do you know how to run a binary in linux?
実行ファイルrun
が配布されます。
実行するだけで良さそうなので、linuxで下記コマンドを実行。
$ ./run Here's your flag! : shaktictf{and_that's_how_you_run_a_linux_binary!}
PYthn [Easy]
Familiar with python?
Author : bl4ck_Widw
PYthn.py
というコードが配布されます。
Z=[] k=[] Q="K78m)hm=|cwsXhbH}uq5w4sJbPrw6" def Fun(inp): st=[] for i in range (len(inp)): st.append(chr(ord(inp[i])^1)) return(''.join(st)) def FuN(inp): for i in range(len(inp)): if(i<11): Z.append(chr(ord(inp[i])+i+5)) else: Z.append(chr(ord(inp[i])+4)) return(''.join(Z)) def fuN(text,s): result = "" for i in range(len(text)): char = text[i] if(char.isnumeric()): result+=(chr(ord(char)-1)) elif(char.isupper()): result += chr((ord(char) + s-65) % 26 + 65) else: result+=(chr(ord(char)^1)) return result X=input("Enter input:") k=FuN(Fun(X)) if(Q!=k): print("NO") else: print("Flag: shaktictf{"+X+"}")
入力値をアレコレしてQ
とおなじになったらflagを表示してくれるらしい。というか正解の入力値そのものがflagっぽい。
逆をするプログラムを書いた。
Q="K78m)hm=|cwsXhbH}uq5w4sJbPrw6" def deFuN(out): Z = [] for i in range(len(out)): if(i<11): Z.append(chr(ord(out[i])-i-5)) else: Z.append(chr(ord(out[i])-4)) return(''.join(Z)) def deFun(out): st=[] for i in range (len(out)): st.append(chr(ord(out[i])^1)) return(''.join(st)) print('Flag: shaktictf{' + deFun(deFuN(Q)) + '}')
実行結果
$ python solve.py Flag: shaktictf{G00d!_c0nTinUe_Expl0r1nG_Mor3}
Damez [Easy]
Oops! There was a sudden crash on Margret's system. She's afraid that she lost the passcode which she needs in urgency for running a simple prog which hopefully was backed up. Could you figure out the passcode and run the program successfully.
Author: dhr33ti
実行ファイルdamez
が配布される。
真面目にradare2で解析しようと思ったんだけど、stringsで出ちゃった。
$ strings damez | grep shaktictf{ shaktictf{K33p_th3_gam3_g0ing_gurl!}
EZ [Easy]
Don’t be afraid to say 'I don't know'. No question is a dumb question :)
Author : bl4ck_Widw
ez.exe
が配布されます。exeファイルかぁ…。
とりあえず雑にghidraに突っ込んでみたところ、
lcZdl_Yoati+Xjn,lN!gGRdNR-R]H`=XjN,lo*+Iv
こんな文字列を発見したのと、main
から呼び出されるsolo
,lol
,goodjob
関数で文字列操作がされているのを見つけたので、reversingできそう。
Enter the Passcode: で入力した文字列を solo
,lol
で変換し、これと
lcZdl_Yoati+Xjn,lN!gGRdNR-R]H`=XjN,lo*+Iv
を比較。等しければ最初の入力値をgoodjob
で更に変換するとflagになる。これならexeファイルを動かす環境がなくても解けそう。ちょっとやる気出た。
読みやすくするためにghidraのdecomplie結果から色々削った各関数はこちら。
int main(int _Argc,char **_Argv,char **_Env) { int iVar1; longlong lVar2; char input [112]; char flag [112]; char input_buff [112]; printf("Enter passcode :"); lVar2 = .text(); fgets(input_buff,0x2a,*(FILE **)(lVar2 + 8)); strcpy(input,input_buff); soloed = solo((longlong)input_buff); loled = (char *)lol(soloed); iVar1 = strcmp(loled,"lcZdl_Yoati+Xjn,lN!gGRdNR-R]H`=XjN,lo*+Iv"); if (iVar1 == 0) { goodjobed = (char *)goodjob((longlong)input); strcpy(flag,goodjobed); printf("Congrats!! \nflag: %s\n",flag); } else { printf("So sorry sister :("); } return 0; } longlong solo(longlong param_1) { int idx = 0; while ((param_1 + idx) != '\0') { (param_1 + idx) = ((param_1 + idx) ^ 1) - 5; idx = idx + 1; } return param_1; } longlong lol(longlong param_1) { char tmp; int idx; idx = 0; while ((param_1 + idx) != '\0') { if ((6 < idx) && (idx < 0x11)) { // 7 ~ 16 tmp = (param_1 + idx); (param_1 + idx) = tmp + '\x01'; (param_1 + idx) = tmp; } if ((-1 < idx) && (idx < 4)) { // 0 ~ 3 tmp = (param_1 + idx); (param_1 + idx) = tmp + -1; (param_1 + idx) = tmp; } if ((3 < idx) && (idx < 7)) { // 4 ~ 6 (param_1 + idx) = (param_1 + idx) + -3; } if ((idx < 0x1e) && (0x10 < idx)) { // 17 ~ 29 (param_1 + idx) = (param_1 + idx) ^ 4; } else { (param_1 + idx) = (param_1 + idx) + '\x05'; } idx = idx + 1; } return param_1; } longlong goodjob(longlong param_1) { int idx = 0; while ((param_1 + idx) != '\0') { if ((idx < 0x14) || (0x1d < idx)) { (param_1 + idx) = (param_1 + idx) + '\x06'; } else { (param_1 + idx) = (param_1 + idx) + '\x05'; } idx = idx + 1; } return param_1; }
これをさっきの順に変換するスクリプト
cipher = "lcZdl_Yoati+Xjn,lN!gGRdNR-R]H`=XjN,lo*+Iv" def de_solo(param): res = '' for i in range(len(param)): res += chr((ord(param[i])+5)^1) return res def de_lol(param): res = '' for i in range(len(param)): converted = param[i] if (0 <= i) and (i < 4): converted = chr(ord(param[i])) # +1 is fake if (4 <= i) and (i < 7): converted = chr(ord(param[i])+3) if (7 <= i) and (i < 17): converted = chr(ord(param[i])) # -1 is fake if (17 <= i) and (i < 30): converted = chr(ord(param[i])^4) else: converted = chr(ord(converted)-5) res += converted return res def goodjob(param): res = '' for i in range(len(param)): if i < 20 or 29 < i: res += chr(ord(param[i]) + 6) else: res += chr(ord(param[i]) + 5) return res de_loled = de_lol(cipher) de_soloed = de_solo(de_loled) flag = goodjob(de_soloed) print(flag)
実行結果
$ python solve.py shaktictf{n0_qu3sT1oN_iS_4_dUmB_qU3st10N}
Yay🙌
fakeの処理に惑わされたりしてdebugに時間かかったけど、解けてよかった!これに結構時間使っちゃった。
REach the Moon [Medium] ※解けていない
Enter the correct flag to reach the moon. Remember flag is in shaktictf{} format
Author : imm0rt4l_5t4rk
実行ファイルmoon
が配布されます。
ghidraに突っ込んでdecompileしてもらいます。
複雑な条件新規が出てきたので、z3で解く問題っぽいなー。どうやって動かすんだっけなー。とソルバを書いてデバッグしているところで競技終了。
https://github.com/Z3Prover/z3:embed:site
このCTFのあとにghidraが古すぎることが判明してアップデートしたので、もう一度ghidraでdecompileをするところから始めます。
undefined8 main(void) { char cVar1; int iVar2; size_t sVar3; long in_FS_OFFSET; char local_88; char local_87; byte local_86; char local_85; char local_84; byte local_83; char local_82; byte local_81; char local_80; byte local_7f; char local_7d; byte local_7c; byte local_7b; char local_7a; char local_79; char local_78; byte local_68; char local_67; char local_66; char local_65; char local_64; char local_63; char local_62; char local_61; byte local_60; byte local_5f; byte local_5e; char local_5d; byte local_5c; byte local_5b; char local_5a; byte local_59; byte local_58; byte local_48; char local_47; char local_46; byte local_45; byte local_44; byte local_43; char local_42; char local_41; byte local_40; char local_3f; undefined local_3e; char local_3d; byte local_3c; char local_3b; byte local_3a; char local_39; char local_38; char local_37; byte local_36; char local_35; undefined local_34; undefined8 local_28; undefined8 local_20; undefined4 local_18; undefined local_14; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_34 = 0; puts( "Hello there! Are you ready for a mission to the moon? Input the correct flag to lead yourspacecraft reach the moon." ); __isoc99_scanf("%s %s",&local_88,&local_68); sVar3 = strlen(&local_88); if (sVar3 == 0x11) { sVar3 = strlen((char *)&local_68); if (sVar3 == 0x11) { local_48 = local_68 ^ local_88 + 0xb9U; local_47 = local_67 + (local_87 >> 2) + -7; local_46 = (local_66 - (local_86 ^ 0x2d)) * '\x03' + -0x14; local_45 = (local_85 - local_65) * '\x02' ^ 0x1b; local_44 = local_64 + local_84 ^ 0xa7; local_43 = local_83 & local_63 + 0x6fU; local_42 = local_82 % local_62 + '3'; local_41 = local_61 + (local_81 ^ 0x61); local_40 = local_60 ^ 0xbcU - local_80; local_3f = (local_7f ^ 0x42) + (local_5f ^ 0x30); local_3e = (undefined) ((int)local_5d >> ((char)((short)(local_7d * 0x10085) >> 0xc) - (local_7d >> 7) & 0x1fU)); local_3d = (local_65 - local_7d) * '#'; local_3c = local_7c ^ local_5c ^ 0x46; local_3b = ~local_5b - local_7c; local_3a = local_5b; cVar1 = local_5a >> 7; local_39 = cVar1 - (local_7b ^ 0xf8); local_38 = local_5a + ((char)((short)(local_5a * 0x1008f) >> 0xe) - cVar1) * -0x73 + local_7a + ((char)((short)(local_7a * 0x10087) >> 0xe) - (local_7a >> 7)) * -0x7a + 'H'; local_37 = ((local_5a + ((char)((short)(local_5a * 0x1008f) >> 0xe) - cVar1) * -0x73) - local_79) + '2'; local_36 = local_78 + ((char)((short)(local_78 * 0x1008b) >> 0xe) - (local_78 >> 7)) * -0x76 & local_59 ^ 10; local_35 = (local_58 ^ 0x1d) + (local_78 >> 2); local_28 = 0x746369746b616873; local_20 = 0x5967346c46307b66; local_18 = 0x7d3f7433; local_14 = 0; iVar2 = strcmp((char *)&local_48,(char *)&local_28); if ((iVar2 == 0) && (local_68 == local_5e)) { puts( "\nWell Done! Your mission to the moon is successful. Did you know just like you,Margaret Hamilton led the software engineering team for Apollo 11 which was the missionwhere humans landed on the moon for the first time." ); printf("\nHere is your flag: %s%s\n",&local_88,&local_68); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; } puts("\nNot there yet"); /* WARNING: Subroutine does not return */ exit(1); } } puts("Long way to go.."); /* WARNING: Subroutine does not return */ exit(1); }
これを解読しながら読みやすくすると
undefined8 main(void) { char cVar1; int iVar2; size_t input_len; char *input1; char *input2; char *secret; undefined8 key; secret[20] = 0; puts( "Hello there! Are you ready for a mission to the moon? Input the correct flag to lead yourspacecraft reach the moon." ); __isoc99_scanf("%s %s",&input1,&input2[0]); input_len = strlen(&input1); if (input_len == 0x11) { // 17 input_len = strlen((char *)&input2[0]); if (input_len == 0x11) { // 17 secret = input2[0] ^ input1 + 0xb9U; secret[1] = input2[1] + (input1[1] >> 2) + -7; secret[2] = (input2[2] - (input1[2] ^ 0x2d)) * '\x03' + -0x14; secret[3] = (input1[3] - input2[3]) * '\x02' ^ 0x1b; secret[4] = input2[4] + input1[4] ^ 0xa7; secret[5] = input1[5] & input2[5] + 0x6fU; secret[6] = input1[6] % input2[6] + '3'; secret[7] = input2[7] + (input1[7] ^ 0x61); secret[8] = input2[8] ^ 0xbcU - input1[8]; secret[9] = (input1[9] ^ 0x42) + (input2[9] ^ 0x30); secret[10] = (undefined) ((int)input2[11] >> ((char)((short)(input1[11] * 0x10085) >> 0xc) - (input1[11] >> 7) & 0x1fU)); secret[11] = (input2[3] - input1[11]) * '#'; secret[12] = input1[12] ^ input2[12] ^ 0x46; secret[13] = ~input2[13] - input1[12]; secret[14] = input2[13]; cVar1 = input2[14] >> 7; secret[15] = cVar1 - (input1[13] ^ 0xf8); secret[16] = input2[14] + ((char)((short)(input2[14] * 0x1008f) >> 0xe) - cVar1) * -0x73 + input1[14] + ((char)((short)(input1[14] * 0x10087) >> 0xe) - (input1[14] >> 7)) * -0x7a + 'H'; secret[17] = ((input2[14] + ((char)((short)(input2[14] * 0x1008f) >> 0xe) - cVar1) * -0x73) - input1[15]) + '2'; secret[18] = input1[16] + ((char)((short)(input1[16] * 0x1008b) >> 0xe) - (input1[16] >> 7)) * -0x76 & input2[15] ^ 10; secret[19] = (input2[16] ^ 0x1d) + (input1[16] >> 2); key[0] = 0x746369746b616873; key[1] = 0x5967346c46307b66; key[2] = 0x7d3f7433; iVar2 = strcmp((char *)&secret,(char *)&key); if ((iVar2 == 0) && (input2[0] == input2[10])) { puts( "\nWell Done! Your mission to the moon is successful. Did you know just like you,Margaret Hamilton led the software engineering team for Apollo 11 which was the missionwhere humans landed on the moon for the first time." ); printf("\nHere is your flag: %s%s\n",&input1,&input2); return 0; } puts("\nNot there yet"); exit(1); } } puts("Long way to go.."); exit(1); }
keyの16進数は、cyberchefで変換すると
あとはこの条件を満たす input1, input2 を探すソルバを書くだけ…!と思ったけど、これがなかなかうまく行かない。競技中に諦めたのは正解だった。
Team-Shaktiのwriteup を見ても、そもそもdecompile結果から何故こうなるのか?という条件式がいくつかあり。元のキレイなコードを見ずにこの式をどうやってかけるのかが知りたい。
後味悪いですが、この問題はこれ以上深堀りなし。z3系の問題はもう少し単純なやつから解けるようになろう。
感想
とりあえず ghidra に突っ込んでdecompileしてもらい、逆処理のスクリプトを書く、みたいな筋肉技しか持っていないので、なかなか時間がかかって辛いことがわかった。
ソロで参加し続けるなら、 angr とか z3 とか、ちゃんとシュッと使えるようにしとかないとなぁ…。(Moon問題はz3でソルバっぽいものを書いてみたけど、普段全然書かないからdebugが全然間に合わずに解けなかった…)-> 時間があっても解けなかったっぽい。
他にもAssemble!
, XOXO
という問題があったみたいだけど、問題回収しておらず復習できませんでした。残念。