好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

Shakti CTF 2020 writeup [Reversing]

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に時間かかったけど、解けてよかった!これに結構時間使っちゃった。

感想

とりあえず ghidra に突っ込んでdecompileしてもらい、逆処理のスクリプトを書く、みたいな筋肉技しか持っていないので、なかなか時間がかかって辛いことがわかった。
ソロで参加し続けるなら、 angr とか z3 とか、ちゃんとシュッと使えるようにしとかないとなぁ…。(Moon問題はz3でソルバっぽいものを書いてみたけど、普段全然書かないからdebugが全然間に合わずに解けなかった…)