چالش اندروید اول - کجایی رمز من؟ دقیقا کجایی؟

توضیحات

چنین نیست که تو بخواهی وارد شوی و همان شود! برای به دست آوردن رمز عبور نیاز داریم کمی زحمت بکشی. رمز عبور طبق قرار قبلی ۴ بخش است و هر بخش با _ جدا شده است. توصیه می‌کنیم هر بخش را جدا به دست بیاوری!
[دانلود فایل]

قالب پرچم در این سوال به صورت parcham{some_l33t_string} است.

حل چالش

گام اول

در این سوال یک پرونده‌ی apk به ما داده شده است:

md5sum PasscodeHell.apk 
6a4523f65c6af326e914d68123a5689f  PasscodeHell.apk

در ابتدای راه اگر به یک شبیه‌ساز دسترسی دارید می‌توانید پس از بارگیری فایل apk آن را نصب نمایید. قبلا توصیه کرده‌ایم که به هیچ عنوان برنامه‌های ناشناخته را در دستگاه شخصی خودتان نصب نکنید.

ما چون به اندروید استدیو دسترسی داشتیم، از ماشین‌های شبیه‌ساز آن استفاده کرده‌ایم. برای این‌که مطمئن شویم از شبیه‌ساز درستی استفاده می‌کنیم، نیاز داریم تا نسخه‌ی SDK که نرم‌افزار با آن ساخته شده است را ببینیم، پس با استفاده از ابزار apktool، این پرونده را در حالت decode به شکل زیر باز می‌کنیم:

apktool d PasscodeHell.apk && cd PasscodeHell/ && ls 
./  ../  AndroidManifest.xml  apktool.yml  original/  res/  smali/

در این همین پوشه با خواندن پرونده‌ی AndroidManifest.xml می‌توانیم نسخه‌ی SDK را ببنیم که برای این برنامه به شکل android:compileSdkVersion="28" است. به همین دلیل ما یک دستگاه گوگل پیکسل با اندروید ۹ آماده و سپس به شکل زیر نرم‌افزار را روی دستگاه نصب کرده‌ایم:

$ adb install PasscodeHell.apk 
Success

پس از باز کردن نرم‌افزار در تلفن همراه شبیه‌سازی شده، با یک نرم‌افزار دریافت گذرواژه مواجه می‌شویم (تصویر اول) که در صورتی که یک رشته‌ی معمولی وارد کنیم، به ما تذکر داده می‌شود که گذرواژه باید چهاربخشی باشد و این بخش‌ها با زیرخط (underline) جدا شده‌اند (تصویر دوم). سپس برای آزمون یک گذرواژه با قالب صحیح وارد می‌کنیم و با عبارت گذرواژه‌ی اشتباه مواجه می‌شویم (تصویر سوم).

نرم‌افزار
نرم‌افزار
قالب گذرواژه
قالب گذرواژه
پیام اشتباه
پیام اشتباه

در این مرحله دیگر به سراغ کدهای برنامه می‌رویم تا ببینیم دقیقا گذرواژه به چه شکلی در برنامه بررسی می‌شود.

گام دوم

در این مرحله ابتدا پرونده‌ی apk را از حالت فشرده خارج می‌کنیم و سپس با استفاده از ابزار dex2jar پرونده‌ی classes.dex را به جاوا تبدیل می‌کنیم.

unzip PasscodeHell.apk 
./tools/dex2jar-2.0/d2j-dex2jar.sh classes.dex

و در نهایت خروجی این دستورات که پرونده‌ی classes-dex2jar.jar را با استفاده از ابزار JD-GUI باز می‌کنیم و مسیر io.parcham.parchampasscode شامل کدهای این برنامه است.

کد جاوا
کد جاوا

ابتدا به سراغ io.parcham.parchampasscode.MainActivity می‌رویم. در این پرونده می‌بینیم که یک دکمه تعریف شده است که پس از کلیک بر روی آن رشته‌ی متنی برای پردازش برداشته می‌شود. در همین مرحله، ابتدا بررسی می‌شود که ۴ زیرخط درون رشته ورودی، وجود دارد یا خیر که در غیر این صورت پیام
Correct passcode format is:\n xxxx_xxxx_xxxx_xxxx نمایش داده می‌شود.

اگر این رشته دارای ۴ زیرخط باشد، رشته تقسیم می‌شود و هر بخش آن به یک تابع از کلاس ShallPass ارسال می‌شود.

ShallPass.hereIAm(arrayOfString[0]);
ShallPass.thisIsMe(arrayOfString[1]);
ShallPass.meAgain(arrayOfString[2]);
ShallPass.andYou(arrayOfString[3]);

اگر خروجی هر چهار تابع درست باشد عبارت Congratz!!!\n This is the flag, you may enter! و در غیر این صورت عبارت Passcode is not correct!
نمایش داده می‌شود.

گام سوم

در این گام به کلاس ShallPass می‌رویم تا چهار تابع را بررسی کنیم.

قبل از این‌که با جزییات توابع را بررسی کنیم، از آن‌جایی که هر چهار تابع با توابعی از کلاس PreShallPass کار می کنند، این کلاس را بررسی می‌کنیم و متوجه می‌شویم که این کلاس، باز هم چهار تابع دارد. نکته‌ی مهم در این کلاس این است که اسامی توابع یکسان است و و فقط براساس نوع ورودی این توابع متمایز می‌شوند.

تابع hereIAm:
این تابع در اصل فقط یک شرط ساده است:

"fSFtMGNsZVdkaTByZG40MFN7bWFoY3JhcA==".equals(PreShallPass.seeMe(paramString))

همان‌طور که مشخص است رشته‌ی fSFtMGNsZVdkaTByZG40MFN7bWFoY3JhcA== که عبارت base64 است با خروجی تابع PreShallPass.seeMe بررسی شده است. همان‌طور که توضیح دادیم بر اساس نوع ورودی تابع PreShallPass.seeMe، یکی از ۴ تابع از کلاس PreShallPass انتخاب می‌شوند. که در اینجا، تابعی که یک رشته ورودی می‌گیرد انتخاب می‌شود.
کد این تابع را در ادامه می‌بینم:

public static String seeMe(String paramString) {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.append(paramString);
  stringBuilder.append("Welc0m!}");
  paramString = (new StringBuilder(stringBuilder.toString())).reverse().toString();
  try {
    return Base64.encodeToString(paramString.getBytes("utf-8"), 2);
  } catch (UnsupportedEncodingException unsupportedEncodingException) {
    unsupportedEncodingException.printStackTrace();
    return "";
  } 
}

این تابع در واقع رشته‌ی Welc0m!} را به ورودی اضافه می‌کند، این رشته را معکوس می کند و سپس از آن base64 می‌گیرد و خروجی را باز می‌گرداند. پس عبارت fSFtMGNsZVdkaTByZG40MFN7bWFoY3JhcA== مربوط به معکوسِ رشته‌ی ترکیب بخش اول پرچم و عبارت Welc0m!} است.
به کمک دستور زیر در ترمینال، به عبارت زیر می‌رسیم:

echo "fSFtMGNsZVdkaTByZG40MFN7bWFoY3JhcA==" | base64 --decode | rev 
parcham{S04ndr0idWelc0m!}

و نتیجه می‌گیریم بخش ابتدای پرچم عبارت parcham{S04ndr0i است. دقت می‌کنیم که زیرخط در این عبارت نیست، چون موقع تقسیم‌بندی آن را حذف کرده است ولی برای ارسال عبارت پرچم به آن نیاز داریم.

تابع thisIsMe:

در این تابع، بار دیگر فراخوانی PreShallPass.seeMe را مشاهده می‌کنیم، ولی این‌بار ورودی این تابع یک آرایه‌عددی است که با نام Queen در ابتدای کلاس ShallPass تعریف شده است. فراخوانی PreShallPass.seeMe با آرایه‌ی عددی یک رشته‌ می‌سازد. در اینجا، با داشتن کد جاوا، به جای تحلیل الگوریتم (که البته خیلی پیچیده هم نیست) یا نوشتن معادل پایتون‌ آن، می‌خواهیم خود کد جاوا را اجرا کنیم و خروجی آن را ببینیم. پس یک برنامه‌ی جاوا که فقط همین تابع را دارد می‌سازیم و خروجی آن را چاپ می کنیم. برنامه به شکل زیر خواهد بود:

public class Main{

      public static void main(String []args){

        int[] paramArrayOfint = {-1602723372, 811074983, -1602723401, 811075023, -949198329, 1053776347, -1602723400, 811074964, -949198243, 1053776336, -1602723353, -949198285, 1053776311};
        for (int i = 0; i < paramArrayOfint.length; i++) {
              paramArrayOfint[i] = paramArrayOfint[i] % 256;
              if (paramArrayOfint[i] < 0)
                paramArrayOfint[i] = paramArrayOfint[i] + 256; 
        } 

        StringBuilder stringBuilder = new StringBuilder();
        int j = paramArrayOfint[0];
        int k = paramArrayOfint[1];
        int m = paramArrayOfint[paramArrayOfint.length - 2];
        int n = paramArrayOfint[paramArrayOfint.length - 1];
        for (int i = 2; i < paramArrayOfint.length - 2; i++) {
              int i1 = paramArrayOfint[i];
              int[] arr = {j, k, m, n};
              stringBuilder.append((char)(arr[(i - 2) % 4] ^ i1));
        } 
        System.out.println(stringBuilder.toString());

      }


}

سپس با استفاده از یک کامپایلر برخط یا اگر جاوا در سامانه‌ی شما نصب است با دستورا زیر خروجی را ببینید:

javac Main.java
java Main 
ch4ll3ng3

و نتیجه می‌گیریم بخش دوم پرچم عبارت ch4ll3ng3 است.

تابع meAgain:

این تابع نیاز به اجرا ندارد و با خواندن کد می‌توان الگوریتم آن را فهمید.

public static void meAgain(String paramString) {
  String str2 = "";
  String str1 = "";
  int i;
  for (i = 0; i < paramString.length(); i += 2) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(str1);
    stringBuilder.append(paramString.charAt(i));
    str1 = stringBuilder.toString();
  } 
  for (i = 1; i < paramString.length(); i += 2) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(str2);
    stringBuilder.append(paramString.charAt(i));
    str2 = stringBuilder.toString();
  } 
  if ("n350U3dFc5".equals(PreShallPass.seeMe(str1, str2))) {
    MainActivity.BufferMe.set(3, true);
    return;
  }
}

در واقع یک ورودی که بخش سوم پرچم است را می‌گیرد و سپس آن را تبدیل به دو زیررشته می‌کند. زیررشته‌ی اول شامل کاراکترهایی با ایندکس زوج و زیررشته‌ی دوم شامل کاراکترهایی در موقعیت فرد است. سپس تابع PreShallPass.seeMe با همین دو زیررشته صدا زده می‌شود، که این دو زیررشته را به هم می‌چسابند و خروجی را n350U3dFc5 مقایسه می‌کند. پس همین رشته را نصف می‌کنیم، یکی در میان به هم اضافه می‌کنیم و به عبارت n33d5F0cU5 می‌رسیم که بخش سوم پرچم است.

تابع andYou:

در نهایت در بخش آخر، بازم هم تابع PreShallPass.seeMe و این بار با سه ورودی صدا زده می‌شود. سه ورودی به ترتیب، ۲ بایت اول، بایت ۲ تا ۹ و سپس بایت ۹ تا ۱۲ بخش چهارم پرچم است.

public static String seeMe(String paramString1, String paramString2, String paramString3) {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.append(paramString1);
  stringBuilder.append("Str0ng");
  paramString1 = stringBuilder.toString();
  stringBuilder = new StringBuilder();
  stringBuilder.append("Remov3Extr4");
  stringBuilder.append(paramString2);
  paramString2 = stringBuilder.toString();
  try {
    stringBuilder = new StringBuilder();
    stringBuilder.append(paramString1);
    stringBuilder.append(paramString2);
    stringBuilder.append(paramString3);
    return Base64.encodeToString(stringBuilder.toString().getBytes("utf-8"), 2);
  } catch (UnsupportedEncodingException unsupportedEncodingException) {
    unsupportedEncodingException.printStackTrace();
    return "";
  } 
}

این تابع که در ادامه آمده است، عبارت Str0ng را به رشته‌‌ی اول، عبارت Remov3Extr4 را به رشته‌ی دوم می‌چسباند و سپس هر سه زیررشته را باهم ترکیب و از آن‌ها base64 می‌گیرد که معادل عبارت YjNTdHIwbmdSZW1vdjNFeHRyNHA0dGkzbnQhIX0= می‌شود. پس از این رشته، را دی‌کد (base64 --decode) می‌کنیم و سپس دو زیررشته‌ی اضافی را حذف و به عبارت b3p4ti3nt!!} می‌رسیم که بخش آخر پرچم است.

گام آخر

خب با داشتن ۴ بخش پرچم آن‌ها را با استفاده از زیرخط بهم می‌چسبانیم و پرچم را ارسال می‌کنیم!!
parcham{S04ndr0id_ch4ll3ng3_n33d5F0cU5_b3p4ti3nt!!}