چالش اندروید اول - کجایی رمز من؟ دقیقا کجایی؟
توضیحات
چنین نیست که تو بخواهی وارد شوی و همان شود! برای به دست آوردن رمز عبور نیاز داریم کمی زحمت بکشی. رمز عبور طبق قرار قبلی ۴ بخش است و هر بخش با _ جدا شده است. توصیه میکنیم هر بخش را جدا به دست بیاوری!
[دانلود فایل]
قالب پرچم در این سوال به صورت 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!!}