Hack.lu CTF 2020 Writeup

この大会は2020/10/23 22:37(JST)~2020/10/25 22:37(JST)に開催されました。
今回もチームで参戦。結果は602点で302チーム中113位でした。
自分で解けた問題をWriteupとして書いておきます。

flagdroid (rev)

apkを解凍し、classes.dexをjarに変換する。

>d2j-dex2jar classes.dex
dex2jar classes.dex -> .\classes-dex2jar.jar

jarファイルをJD-GUIデコンパイルする。

package lu.hack.Flagdroid;

import android.content.res.Resources;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity
{
  static
  {
    System.loadLibrary("native-lib");
  }

  private boolean checkSplit1(String paramString)
  {
    byte[] arrayOfByte = Base64.decode(getResources().getString(2131492894), 0);
    try
    {
      String str = new java/lang/String;
      str.<init>(arrayOfByte, "UTF-8");
      boolean bool = str.equals(paramString);
      return bool;
    }
    catch (java.io.UnsupportedEncodingException paramString)
    {
    }
    return false;
  }

  private boolean checkSplit2(String paramString)
  {
    try
    {
      paramString = paramString.toCharArray();
      byte[] arrayOfByte = "hack.lu20".getBytes("UTF-8");
      if (paramString.length != 9)
        return false;
      for (int i = 0; i < 9; ++i)
      {
        paramString[i] = (char)(char)(paramString[i] + i);
        paramString[i] = (char)(char)(paramString[i] ^ arrayOfByte[i]);
      }
      boolean bool = String.valueOf(paramString).equals("\31TT:\315ñHG");
      return bool;
    }
    catch (java.io.UnsupportedEncodingException paramString)
    {
    }
    return false;
  }

  private boolean checkSplit3(String paramString)
  {
    paramString = paramString.toLowerCase();
    if (paramString.length() != 8)
      return false;
    if (!(paramString.substring(0, 4).equals("h4rd")))
      return false;
    return md5(paramString).equals("6d90ca30c5de200fe9f671abb2dd704e");
  }

  private boolean checkSplit4(String paramString)
  {
    return paramString.equals(stringFromJNI());
  }

  public static String md5(String paramString)
  {
    try
    {
      Object localObject = MessageDigest.getInstance("MD5");
      ((MessageDigest)localObject).update(paramString.getBytes());
      paramString = ((MessageDigest)localObject).digest();
      localObject = new java/lang/StringBuilder;
      ((StringBuilder)localObject).<init>();
      int i = paramString.length;
      for (int j = 0; j < i; ++j)
      {
        int k = paramString[j];
        StringBuilder localStringBuilder = new java/lang/StringBuilder;
        localStringBuilder.<init>(Integer.toHexString(k & 0xFF));
        while (localStringBuilder.length() < 2)
          localStringBuilder.insert(0, "0");
        ((StringBuilder)localObject).append(localStringBuilder);
      }
      paramString = ((StringBuilder)localObject).toString();
      return paramString;
    }
    catch (NoSuchAlgorithmException paramString)
    {
      paramString.printStackTrace();
    }
    return ((String)"");
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2131361820);
    TextView localTextView = (TextView)findViewById(2131165420);
    paramBundle = (TextView)findViewById(2131165419);
    localTextView.setVisibility(4);
    paramBundle.setVisibility(4);
    ((Button)findViewById(2131165271)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramView)
      {
        TextView localTextView = (TextView)MainActivity.this.findViewById(2131165420);
        paramView = (TextView)MainActivity.this.findViewById(2131165419);
        Object localObject = ((EditText)MainActivity.this.findViewById(2131165380)).getText().toString();
        localObject = Pattern.compile("flag\\{(.*)\\}").matcher((CharSequence)localObject);
        if (((Matcher)localObject).find())
        {
          localObject = ((Matcher)localObject).group().replace("flag{", "").replace("}", "").split("_");
          if (localObject.length == 4)
          {
            boolean bool1 = MainActivity.this.checkSplit1(localObject[0]);
            boolean bool2 = MainActivity.this.checkSplit2(localObject[1]);
            boolean bool3 = MainActivity.this.checkSplit3(localObject[2]);
            boolean bool4 = MainActivity.this.checkSplit4(localObject[3]);
            if ((bool1) && (bool2) && (bool3) && (bool4))
            {
              localTextView.setVisibility(4);
              paramView.setVisibility(0);
              return;
            }
          }
        }
        localTextView.setVisibility(0);
        paramView.setVisibility(4);
      }
    });
  }

  public native String stringFromJNI();
}


public final class R
{
    :
  public static final class string
  {
    public static final int abc_action_bar_home_description = 2131492864;
    public static final int abc_action_bar_up_description = 2131492865;
    public static final int abc_action_menu_overflow_description = 2131492866;
    public static final int abc_action_mode_done = 2131492867;
    public static final int abc_activity_chooser_view_see_all = 2131492868;
    public static final int abc_activitychooserview_choose_application = 2131492869;
    public static final int abc_capital_off = 2131492870;
    public static final int abc_capital_on = 2131492871;
    public static final int abc_menu_alt_shortcut_label = 2131492872;
    public static final int abc_menu_ctrl_shortcut_label = 2131492873;
    public static final int abc_menu_delete_shortcut_label = 2131492874;
    public static final int abc_menu_enter_shortcut_label = 2131492875;
    public static final int abc_menu_function_shortcut_label = 2131492876;
    public static final int abc_menu_meta_shortcut_label = 2131492877;
    public static final int abc_menu_shift_shortcut_label = 2131492878;
    public static final int abc_menu_space_shortcut_label = 2131492879;
    public static final int abc_menu_sym_shortcut_label = 2131492880;
    public static final int abc_prepend_shortcut_label = 2131492881;
    public static final int abc_search_hint = 2131492882;
    public static final int abc_searchview_description_clear = 2131492883;
    public static final int abc_searchview_description_query = 2131492884;
    public static final int abc_searchview_description_search = 2131492885;
    public static final int abc_searchview_description_submit = 2131492886;
    public static final int abc_searchview_description_voice = 2131492887;
    public static final int abc_shareactionprovider_share_with = 2131492888;
    public static final int abc_shareactionprovider_share_with_application = 2131492889;
    public static final int abc_toolbar_collapse_description = 2131492890;
    public static final int app_name = 2131492891;
    public static final int check = 2131492892;
    public static final int correct = 2131492893;
    public static final int encoded = 2131492894;
    public static final int enter_secret = 2131492895;
    public static final int search_menu_title = 2131492896;
    public static final int status_bar_notification_info_overflow = 2131492897;
    public static final int welcome_msg = 2131492898;
    public static final int wrong_try_again = 2131492899;
  }
    :
}

"flag{", "}"を除き、"_"区切りで4つの部分がある。

【checkSplit1】
Bytecode Viewerでも見て、encodedをリソースから探し、base64デコードする。
<string name="encoded">dEg0VA==</string>

$ echo dEg0VA== | base64 -d
tH4T

【checkSplit2】
各文字についてインデックスを足して、"hack.lu20"とXORをしたら、"\31TT:\315ñHG"になる。
→ASCIIコードでこうなるものがない。

【checkSplit3】
・8文字
・前半4文字は"h4rd"
・md5が6d90ca30c5de200fe9f671abb2dd704e
import hashlib
import itertools

chars = ''
for code in range(32, 65):
    chars += chr(code)
for code in range(91, 127):
    chars += chr(code)

for c in itertools.product(chars, repeat=4):
    word = 'h4rd' + ''.join(c)
    if hashlib.md5(word).hexdigest() == '6d90ca30c5de200fe9f671abb2dd704e':
        print word
        break

実行結果は以下の通り。
h4rd~huh

【checkSplit4】
libnative-lib.soをGhidraでデコンパイルする。

void Java_lu_hack_Flagdroid_MainActivity_stringFromJNI(int *param_1)

{
  undefined4 *puVar1;
  
  puVar1 = (undefined4 *)malloc(0xd);
  *(undefined *)(puVar1 + 2) = 0x74;
  *(undefined4 *)((int)puVar1 + 9) = 0x29383f;
  puVar1[1] = 0x312d5334;
  *puVar1 = 0x777e7230;
  (**(code **)(*param_1 + 0x29c))(param_1,puVar1);
  return;
}
>>> '777e7230'.decode('hex')[::-1]
'0r~w'
>>> '312d5334'.decode('hex')[::-1]
'4S-1'
>>> '74'.decode('hex')[::-1]
't'
>>> '29383f'.decode('hex')[::-1]
'?8)'

→0r~w4S-1t?8)

【checkSplit2】が合わないので、別の方法でデコンパイルする。jarファイルを解凍し、jadデコンパイルしてみる。

>jad -a MainActivity.class
Parsing MainActivity.class...Parsing inner class MainActivity$1.class... Generating MainActivity.jad
Couldn't resolve all exception handlers in method checkSplit2
Overlapped try statements detected. Not all exception handlers will be resolved in the method md5
Couldn't fully decompile method md5
Couldn't resolve all exception handlers in method md5

あまりうまくいっていないが、中を見てみると、こう書かれている。

flag = String.valueOf(s).equals("\037TT:\0375\361HG");

比較している文字列が先ほどのものと違う。これで元の文字列を探すと、見つかった。

w45N-T~so
flag{tH4T_w45N-T~so_h4rd~huh_0r~w4S-1t?8)}