UC براؤزر میں کمزوریوں کی تلاش ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

تعارف

مارچ کے آخر میں ہم اطلاع دی، کہ انہوں نے UC براؤزر میں غیر تصدیق شدہ کوڈ کو لوڈ کرنے اور چلانے کی پوشیدہ صلاحیت دریافت کی۔ آج ہم تفصیل سے دیکھیں گے کہ یہ ڈاؤن لوڈ کیسے ہوتا ہے اور ہیکرز اسے اپنے مقاصد کے لیے کیسے استعمال کر سکتے ہیں۔

کچھ عرصہ پہلے، UC براؤزر کی تشہیر کی گئی تھی اور بہت جارحانہ طریقے سے تقسیم کیا گیا تھا: اسے میلویئر کا استعمال کرتے ہوئے صارفین کے آلات پر انسٹال کیا گیا تھا، مختلف سائٹس سے ویڈیو فائلز کی آڑ میں تقسیم کیا گیا تھا (یعنی صارفین کا خیال تھا کہ وہ ڈاؤن لوڈ کر رہے ہیں، مثال کے طور پر، ایک فحش ویڈیو، لیکن اس کے بجائے اس براؤزر کے ساتھ ایک APK موصول ہوا)، پیغامات کے ساتھ خوفناک بینرز کا استعمال کیا کہ براؤزر پرانا، کمزور، اور اس جیسی چیزیں ہیں۔ VK پر سرکاری UC براؤزر گروپ میں موجود ہے۔ موضوع، جس میں صارفین غیر منصفانہ اشتہارات کے بارے میں شکایت کر سکتے ہیں، وہاں بہت سی مثالیں موجود ہیں۔ 2016 میں بھی تھا ویڈیو اشتہارات روسی میں (ہاں، اشتہار کو مسدود کرنے والے براؤزر کے لیے اشتہار)۔

لکھنے کے وقت، یو سی براؤزر کی گوگل پلے پر 500 سے زیادہ تنصیبات ہیں۔ یہ متاثر کن ہے - صرف گوگل کروم میں زیادہ ہے۔ تجزیوں میں آپ اشتہارات کے بارے میں کافی شکایات دیکھ سکتے ہیں اور گوگل پلے پر کچھ ایپلی کیشنز کو ری ڈائریکٹ کر سکتے ہیں۔ یہ ہماری تحقیق کی وجہ تھی: ہم نے یہ دیکھنے کا فیصلہ کیا کہ آیا UC براؤزر کچھ برا کر رہا ہے۔ اور یہ پتہ چلا کہ وہ کرتا ہے!

ایپلیکیشن کوڈ میں، قابل عمل کوڈ کو ڈاؤن لوڈ اور چلانے کی صلاحیت دریافت ہوئی، جو کہ ایپلی کیشنز شائع کرنے کے قوانین کے خلاف ہے۔ گوگل پلے پر۔ قابل عمل کوڈ ڈاؤن لوڈ کرنے کے علاوہ، UC براؤزر غیر محفوظ طریقے سے ایسا کرتا ہے، جسے MitM حملہ شروع کرنے کے لیے استعمال کیا جا سکتا ہے۔ دیکھتے ہیں کہ کیا ہم ایسا حملہ کر سکتے ہیں۔

نیچے لکھی گئی ہر چیز UC براؤزر کے اس ورژن سے متعلق ہے جو مطالعہ کے وقت گوگل پلے پر دستیاب تھا:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-файла: f5edb2243413c777172f6362876041eb0c3a928c

حملہ ویکٹر

UC براؤزر مینی فیسٹ میں آپ خود وضاحتی نام کے ساتھ ایک سروس تلاش کر سکتے ہیں۔ com.uc.deployment.UpgradeDeployService.

    <service android_exported="false" android_name="com.uc.deployment.UpgradeDeployService" android_process=":deploy" />

جب یہ سروس شروع ہوتی ہے، براؤزر POST کی درخواست کرتا ہے۔ puds.ucweb.com/upgrade/index.xhtmlجو شروع ہونے کے کچھ دیر بعد ٹریفک میں دیکھا جا سکتا ہے۔ جواب میں، اسے کچھ اپ ڈیٹ یا نیا ماڈیول ڈاؤن لوڈ کرنے کا حکم مل سکتا ہے۔ تجزیہ کے دوران، سرور نے ایسی کوئی کمانڈ نہیں دی، لیکن ہم نے دیکھا کہ جب ہم براؤزر میں پی ڈی ایف کھولنے کی کوشش کرتے ہیں، تو یہ اوپر بتائے گئے ایڈریس پر دوسری درخواست کرتا ہے، جس کے بعد وہ مقامی لائبریری کو ڈاؤن لوڈ کرتا ہے۔ حملے کو انجام دینے کے لیے، ہم نے یوسی براؤزر کی اس خصوصیت کو استعمال کرنے کا فیصلہ کیا: مقامی لائبریری کا استعمال کرتے ہوئے پی ڈی ایف کھولنے کی اہلیت، جو کہ APK میں نہیں ہے اور اگر ضروری ہو تو اسے انٹرنیٹ سے ڈاؤن لوڈ کیا جاتا ہے۔ یہ بات قابل غور ہے کہ، نظریاتی طور پر، UC براؤزر کو صارف کے تعامل کے بغیر کچھ ڈاؤن لوڈ کرنے پر مجبور کیا جا سکتا ہے - اگر آپ براؤزر کے شروع ہونے کے بعد اس پر عمل درآمد کرنے والی درخواست پر ایک اچھی طرح سے جواب فراہم کرتے ہیں۔ لیکن ایسا کرنے کے لیے، ہمیں سرور کے ساتھ تعامل کے پروٹوکول کا مزید تفصیل سے مطالعہ کرنے کی ضرورت ہے، اس لیے ہم نے فیصلہ کیا کہ پی ڈی ایف کے ساتھ کام کرنے کے لیے لائبریری کو تبدیل کرنا اور روکے ہوئے جواب میں ترمیم کرنا آسان ہوگا۔

لہذا، جب کوئی صارف براہ راست براؤزر میں پی ڈی ایف کھولنا چاہتا ہے، تو ٹریفک میں درج ذیل درخواستیں دیکھی جا سکتی ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

سب سے پہلے ایک POST کی درخواست ہے۔ puds.ucweb.com/upgrade/index.xhtmlجس کے بعد
پی ڈی ایف اور آفس فارمیٹس دیکھنے کے لیے لائبریری کے ساتھ ایک آرکائیو ڈاؤن لوڈ کیا جاتا ہے۔ یہ سمجھنا منطقی ہے کہ پہلی درخواست سسٹم کے بارے میں معلومات منتقل کرتی ہے (کم از کم مطلوبہ لائبریری فراہم کرنے کے لیے فن تعمیر)، اور اس کے جواب میں براؤزر لائبریری کے بارے میں کچھ معلومات حاصل کرتا ہے جسے ڈاؤن لوڈ کرنے کی ضرورت ہے: ایڈریس اور، ممکنہ طور پر ، اس کے علاوہ کچھ اور. مسئلہ یہ ہے کہ یہ درخواست انکرپٹڈ ہے۔

ٹکڑے کی درخواست کریں۔

جواب کا ٹکڑا

UC براؤزر میں کمزوریوں کی تلاش ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

لائبریری خود زپ میں پیک کی گئی ہے اور انکرپٹ نہیں ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ٹریفک ڈکرپشن کوڈ تلاش کریں۔

آئیے سرور کے جواب کو سمجھنے کی کوشش کرتے ہیں۔ آئیے کلاس کوڈ کو دیکھتے ہیں۔ com.uc.deployment.UpgradeDeployService: طریقہ سے onStartCommand کے پاس جاؤ com.uc.deployment.bx، اور اس سے com.uc.browser.core.dcfe:

    public final void e(l arg9) {
int v4_5;
String v3_1;
byte[] v3;
byte[] v1 = null;
if(arg9 == null) {
v3 = v1;
}
else {
v3_1 = arg9.iGX.ipR;
StringBuilder v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]product:");
v4.append(arg9.iGX.ipR);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]version:");
v4.append(arg9.iGX.iEn);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]upgrade_type:");
v4.append(arg9.iGX.mMode);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]force_flag:");
v4.append(arg9.iGX.iEo);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_mode:");
v4.append(arg9.iGX.iDQ);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_type:");
v4.append(arg9.iGX.iEr);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_state:");
v4.append(arg9.iGX.iEp);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_file:");
v4.append(arg9.iGX.iEq);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apk_md5:");
v4.append(arg9.iGX.iEl);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_type:");
v4.append(arg9.mDownloadType);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_group:");
v4.append(arg9.mDownloadGroup);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_path:");
v4.append(arg9.iGH);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_child_version:");
v4.append(arg9.iGX.iEx);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_series:");
v4.append(arg9.iGX.iEw);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_arch:");
v4.append(arg9.iGX.iEt);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp3:");
v4.append(arg9.iGX.iEv);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp:");
v4.append(arg9.iGX.iEu);
ArrayList v3_2 = arg9.iGX.iEz;
if(v3_2 != null && v3_2.size() != 0) {
Iterator v3_3 = v3_2.iterator();
while(v3_3.hasNext()) {
Object v4_1 = v3_3.next();
StringBuilder v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_name:");
v5.append(((au)v4_1).getName());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_name:");
v5.append(((au)v4_1).aDA());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_code:");
v5.append(((au)v4_1).gBl);
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_req_type:");
v5.append(((au)v4_1).gBq);
}
}
j v3_4 = new j();
m.b(v3_4);
h v4_2 = new h();
m.b(v4_2);
ay v5_1 = new ay();
v3_4.hS("");
v3_4.setImsi("");
v3_4.hV("");
v5_1.bPQ = v3_4;
v5_1.bPP = v4_2;
v5_1.yr(arg9.iGX.ipR);
v5_1.gBF = arg9.iGX.mMode;
v5_1.gBI = arg9.iGX.iEz;
v3_2 = v5_1.gAr;
c.aBh();
v3_2.add(g.fs("os_ver", c.getRomInfo()));
v3_2.add(g.fs("processor_arch", com.uc.b.a.a.c.getCpuArch()));
v3_2.add(g.fs("cpu_arch", com.uc.b.a.a.c.Pb()));
String v4_3 = com.uc.b.a.a.c.Pd();
v3_2.add(g.fs("cpu_vfp", v4_3));
v3_2.add(g.fs("net_type", String.valueOf(com.uc.base.system.a.Jo())));
v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
v3_2.add(g.fs("plugin_ver", arg9.iGX.iEn));
v3_2.add(g.fs("target_lang", arg9.iGX.iEs));
v3_2.add(g.fs("vitamio_cpu_arch", arg9.iGX.iEt));
v3_2.add(g.fs("vitamio_vfp", arg9.iGX.iEu));
v3_2.add(g.fs("vitamio_vfp3", arg9.iGX.iEv));
v3_2.add(g.fs("plugin_child_ver", arg9.iGX.iEx));
v3_2.add(g.fs("ver_series", arg9.iGX.iEw));
v3_2.add(g.fs("child_ver", r.aVw()));
v3_2.add(g.fs("cur_ver_md5", arg9.iGX.iEl));
v3_2.add(g.fs("cur_ver_signature", SystemHelper.getUCMSignature()));
v3_2.add(g.fs("upgrade_log", i.bjt()));
v3_2.add(g.fs("silent_install", String.valueOf(arg9.iGX.iDQ)));
v3_2.add(g.fs("silent_state", String.valueOf(arg9.iGX.iEp)));
v3_2.add(g.fs("silent_file", arg9.iGX.iEq));
v3_2.add(g.fs("silent_type", String.valueOf(arg9.iGX.iEr)));
v3_2.add(g.fs("cpu_archit", com.uc.b.a.a.c.Pc()));
v3_2.add(g.fs("cpu_set", SystemHelper.getCpuInstruction()));
boolean v4_4 = v4_3 == null || !v4_3.contains("neon") ? false : true;
v3_2.add(g.fs("neon", String.valueOf(v4_4)));
v3_2.add(g.fs("cpu_cores", String.valueOf(com.uc.b.a.a.c.Jl())));
v3_2.add(g.fs("ram_1", String.valueOf(com.uc.b.a.a.h.Po())));
v3_2.add(g.fs("totalram", String.valueOf(com.uc.b.a.a.h.OL())));
c.aBh();
v3_2.add(g.fs("rom_1", c.getRomInfo()));
v4_5 = e.getScreenWidth();
int v6 = e.getScreenHeight();
StringBuilder v7 = new StringBuilder();
v7.append(v4_5);
v7.append("*");
v7.append(v6);
v3_2.add(g.fs("ss", v7.toString()));
v3_2.add(g.fs("api_level", String.valueOf(Build$VERSION.SDK_INT)));
v3_2.add(g.fs("uc_apk_list", SystemHelper.getUCMobileApks()));
Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
while(v4_6.hasNext()) {
Object v6_1 = v4_6.next();
v3_2.add(g.fs(((Map$Entry)v6_1).getKey(), ((Map$Entry)v6_1).getValue()));
}
v3 = v5_1.toByteArray();
}
if(v3 == null) {
this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
return;
}
v4_5 = this.iGY.iGw ? 0x1F : 0;
if(v3 == null) {
}
else {
v3 = g.i(v4_5, v3);
if(v3 == null) {
}
else {
v1 = new byte[v3.length + 16];
byte[] v6_2 = new byte[16];
Arrays.fill(v6_2, 0);
v6_2[0] = 0x5F;
v6_2[1] = 0;
v6_2[2] = ((byte)v4_5);
v6_2[3] = -50;
System.arraycopy(v6_2, 0, v1, 0, 16);
System.arraycopy(v3, 0, v1, 16, v3.length);
}
}
if(v1 == null) {
this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
return;
}
if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
this.iGY.iGI.a(arg9, "up_url", "yes", "fail");
return;
}
StringBuilder v0 = new StringBuilder("[");
v0.append(arg9.iGX.ipR);
v0.append("]url:");
v0.append(this.iGY.mUpgradeUrl);
com.uc.browser.core.d.c.i v0_1 = this.iGY.iGI;
v3_1 = this.iGY.mUpgradeUrl;
com.uc.base.net.e v0_2 = new com.uc.base.net.e(new com.uc.browser.core.d.c.i$a(v0_1, arg9));
v3_1 = v3_1.contains("?") ? v3_1 + "&dataver=pb" : v3_1 + "?dataver=pb";
n v3_5 = v0_2.uc(v3_1);
m.b(v3_5, false);
v3_5.setMethod("POST");
v3_5.setBodyProvider(v1);
v0_2.b(v3_5);
this.iGY.iGI.a(arg9, "up_null", "yes", "success");
this.iGY.iGI.b(arg9);
}

ہم یہاں POST درخواست کی تشکیل دیکھتے ہیں۔ ہم 16 بائٹس کی ایک صف کی تخلیق اور اس کی بھرائی پر توجہ دیتے ہیں: 0x5F, 0, 0x1F, -50 (=0xCE)۔ ہم نے اوپر کی درخواست میں جو دیکھا اس کے موافق ہے۔

اسی کلاس میں آپ ایک نیسٹڈ کلاس دیکھ سکتے ہیں جس کا ایک اور دلچسپ طریقہ ہے:

        public final void a(l arg10, byte[] arg11) {
f v0 = this.iGQ;
StringBuilder v1 = new StringBuilder("[");
v1.append(arg10.iGX.ipR);
v1.append("]:UpgradeSuccess");
byte[] v1_1 = null;
if(arg11 == null) {
}
else if(arg11.length < 16) {
}
else {
if(arg11[0] != 0x60 && arg11[3] != 0xFFFFFFD0) {
goto label_57;
}
int v3 = 1;
int v5 = arg11[1] == 1 ? 1 : 0;
if(arg11[2] != 1 && arg11[2] != 11) {
if(arg11[2] == 0x1F) {
}
else {
v3 = 0;
}
}
byte[] v7 = new byte[arg11.length - 16];
System.arraycopy(arg11, 16, v7, 0, v7.length);
if(v3 != 0) {
v7 = g.j(arg11[2], v7);
}
if(v7 == null) {
goto label_57;
}
if(v5 != 0) {
v1_1 = g.P(v7);
goto label_57;
}
v1_1 = v7;
}
label_57:
if(v1_1 == null) {
v0.iGY.iGI.a(arg10, "up_decrypt", "yes", "fail");
return;
}
q v11 = g.b(arg10, v1_1);
if(v11 == null) {
v0.iGY.iGI.a(arg10, "up_decode", "yes", "fail");
return;
}
if(v0.iGY.iGt) {
v0.d(arg10);
}
if(v0.iGY.iGo != null) {
v0.iGY.iGo.a(0, ((o)v11));
}
if(v0.iGY.iGs) {
v0.iGY.a(((o)v11));
v0.iGY.iGI.a(v11, "up_silent", "yes", "success");
v0.iGY.iGI.a(v11);
return;
}
v0.iGY.iGI.a(v11, "up_silent", "no", "success");
}
}

طریقہ ان پٹ کے طور پر بائٹس کی ایک صف لیتا ہے اور چیک کرتا ہے کہ صفر بائٹ 0x60 ہے یا تیسرا بائٹ 0xD0 ہے، اور دوسرا بائٹ 1، 11 یا 0x1F ہے۔ ہم سرور کے جواب کو دیکھتے ہیں: صفر بائٹ 0x60 ہے، دوسرا 0x1F، تیسرا 0x60 ہے۔ ایسا لگتا ہے کہ ہمیں کیا ضرورت ہے۔ لائنوں کے مطابق ("up_decrypt"، مثال کے طور پر)، یہاں ایک طریقہ بلایا جانا چاہیے جو سرور کے جواب کو ڈکرپٹ کر دے گا۔
آئیے طریقہ کار کی طرف چلتے ہیں۔ جی جے. نوٹ کریں کہ پہلی دلیل آفسیٹ 2 پر بائٹ ہے (یعنی ہمارے معاملے میں 0x1F)، اور دوسرا بغیر سرور کا جواب ہے۔
پہلے 16 بائٹس۔

     public static byte[] j(int arg1, byte[] arg2) {
if(arg1 == 1) {
arg2 = c.c(arg2, c.adu);
}
else if(arg1 == 11) {
arg2 = m.aF(arg2);
}
else if(arg1 != 0x1F) {
}
else {
arg2 = EncryptHelper.decrypt(arg2);
}
return arg2;
}

ظاہر ہے، یہاں ہم ایک ڈکرپشن الگورتھم کا انتخاب کرتے ہیں، اور وہی بائٹ جو ہمارے
کیس 0x1F کے برابر، تین ممکنہ اختیارات میں سے ایک کو ظاہر کرتا ہے۔

ہم کوڈ کا تجزیہ کرتے رہتے ہیں۔ چند چھلانگوں کے بعد ہم خود کو ایک ایسے طریقہ میں پاتے ہیں جس میں خود وضاحتی نام ہے۔ decryptBytesByKey.

یہاں دو مزید بائٹس ہمارے جواب سے الگ ہیں، اور ان سے ایک تار حاصل کیا گیا ہے۔ یہ واضح ہے کہ اس طرح پیغام کو ڈکرپٹ کرنے کے لیے کلید کا انتخاب کیا جاتا ہے۔

    private static byte[] decryptBytesByKey(byte[] bytes) {
byte[] v0 = null;
if(bytes != null) {
try {
if(bytes.length < EncryptHelper.PREFIX_BYTES_SIZE) {
}
else if(bytes.length == EncryptHelper.PREFIX_BYTES_SIZE) {
return v0;
}
else {
byte[] prefix = new byte[EncryptHelper.PREFIX_BYTES_SIZE];  // 2 байта
System.arraycopy(bytes, 0, prefix, 0, prefix.length);
String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Выбор ключа
if(keyId == null) {
return v0;
}
else {
a v2 = EncryptHelper.ayL();
if(v2 == null) {
return v0;
}
else {
byte[] enrypted = new byte[bytes.length - EncryptHelper.PREFIX_BYTES_SIZE];
System.arraycopy(bytes, EncryptHelper.PREFIX_BYTES_SIZE, enrypted, 0, enrypted.length);
return v2.l(keyId, enrypted);
}
}
}
}
catch(SecException v7_1) {
EncryptHelper.handleDecryptException(((Throwable)v7_1), v7_1.getErrorCode());
return v0;
}
catch(Throwable v7) {
EncryptHelper.handleDecryptException(v7, 2);
return v0;
}
}
return v0;
}

آگے دیکھتے ہوئے، ہم نوٹ کرتے ہیں کہ اس مرحلے پر ہمیں ابھی تک کوئی کلید نہیں ملی، بلکہ صرف اس کا "شناخت کنندہ"۔ چابی حاصل کرنا کچھ زیادہ ہی پیچیدہ ہے۔

اگلے طریقے میں، موجودہ میں دو مزید پیرامیٹرز شامل کیے جاتے ہیں، ان میں سے چار بناتے ہیں: جادو نمبر 16، کلیدی شناخت کنندہ، خفیہ کردہ ڈیٹا، اور ایک ناقابل فہم تار (ہمارے معاملے میں، خالی)۔

    public final byte[] l(String keyId, byte[] encrypted) throws SecException {
return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, "");
}

تبدیلیوں کی ایک سیریز کے بعد ہم طریقہ پر پہنچتے ہیں۔ staticBinarySafeDecryptNoB64 انٹرفیس com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent۔ مین ایپلیکیشن کوڈ میں کوئی کلاس نہیں ہے جو اس انٹرفیس کو نافذ کرتی ہے۔ فائل میں ایسی کلاس ہے۔ lib/armeabi-v7a/libsgmain.so, جو اصل میں ایک .so نہیں ہے بلکہ ایک .jar ہے۔ ہم جس طریقہ کار میں دلچسپی رکھتے ہیں اسے مندرجہ ذیل طور پر لاگو کیا جاتا ہے:

package com.alibaba.wireless.security.a.i;
// ...
public class a implements IStaticDataEncryptComponent {
private ISecurityGuardPlugin a;
// ...
private byte[] a(int mode, int magicInt, int xzInt, String keyId, byte[] encrypted, String magicString) {
return this.a.getRouter().doCommand(10601, new Object[]{Integer.valueOf(mode), Integer.valueOf(magicInt), Integer.valueOf(xzInt), keyId, encrypted, magicString});
}
// ...
private byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
return this.a(2, magicInt, 0, keyId, encrypted, magicString);
}
// ...
public byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) throws SecException {
if(keyId != null && keyId.length() > 0 && magicInt >= 0 && magicInt < 19 && encrypted != null && encrypted.length > 0) {
return this.b(magicInt, keyId, encrypted, magicString);
}
throw new SecException("", 301);
}
//...
}

یہاں ہمارے پیرامیٹرز کی فہرست دو مزید عدد کے ساتھ مکمل کی گئی ہے: 2 اور 0۔
ہر چیز، 2 کا مطلب ہے ڈکرپشن، جیسا کہ طریقہ میں ہے۔ ڈو فائنل نظام کی کلاس javax.crypto.Cipher. اور یہ سب کچھ 10601 نمبر کے ساتھ ایک مخصوص راؤٹر پر منتقل کیا جاتا ہے - یہ بظاہر کمانڈ نمبر ہے۔

ٹرانزیشن کی اگلی زنجیر کے بعد ہمیں ایک کلاس ملتی ہے جو انٹرفیس کو لاگو کرتی ہے۔ IRouterComponent اور طریقہ doCommand:

package com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
public class a implements IRouterComponent {
public a() {
super();
}
public Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}

اور کلاس بھی جے این آئی سی لائبریری، جس میں مقامی طریقہ کا اعلان کیا گیا ہے۔ doCommandNative:

package com.taobao.wireless.security.adapter;
public class JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}

اس کا مطلب ہے کہ ہمیں مقامی کوڈ میں ایک طریقہ تلاش کرنے کی ضرورت ہے۔ doCommandNative. اور یہیں سے مزہ شروع ہوتا ہے۔

مشینی کوڈ کی الجھن

فائل میں libsgmain.so (جو دراصل ایک .jar ہے اور جس میں ہمیں کچھ انکرپشن سے متعلق انٹرفیس کا نفاذ اوپر ہی ملا ہے) ایک مقامی لائبریری ہے: libsgmainso-6.4.36.so. ہم اسے IDA میں کھولتے ہیں اور غلطیوں کے ساتھ ڈائیلاگ باکسز کا ایک گروپ حاصل کرتے ہیں۔ مسئلہ یہ ہے کہ سیکشن ہیڈر ٹیبل غلط ہے۔ یہ تجزیہ کو پیچیدہ کرنے کے مقصد سے کیا جاتا ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

لیکن اس کی ضرورت نہیں ہے: ELF فائل کو صحیح طریقے سے لوڈ کرنے اور اس کا تجزیہ کرنے کے لیے، ایک پروگرام ہیڈر ٹیبل کافی ہے۔ لہذا، ہم صرف سیکشن ٹیبل کو حذف کرتے ہیں، ہیڈر میں متعلقہ فیلڈز کو صفر کرتے ہوئے.

UC براؤزر میں کمزوریوں کی تلاش ہے۔

IDA میں فائل کو دوبارہ کھولیں۔

جاوا ورچوئل مشین کو یہ بتانے کے دو طریقے ہیں کہ اصل لائبریری میں جاوا کوڈ میں مقامی کے طور پر اعلان کردہ طریقہ کا نفاذ کہاں واقع ہے۔ سب سے پہلے اسے ایک پرجاتی کا نام دینا ہے۔ Java_package_name_ClassName_MethodName.

دوسرا لائبریری کو لوڈ کرتے وقت اسے رجسٹر کرنا ہے (فنکشن میں JNI_Onload)
فنکشن کال کا استعمال کرتے ہوئے RegisterNatives.

ہمارے معاملے میں، اگر ہم پہلا طریقہ استعمال کرتے ہیں، تو نام اس طرح ہونا چاہئے: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

برآمد شدہ فنکشنز میں ایسا کوئی فنکشن نہیں ہے، جس کا مطلب ہے کہ آپ کو کال تلاش کرنے کی ضرورت ہے۔ RegisterNatives.
چلو فنکشن کی طرف چلتے ہیں۔ JNI_Onload اور ہم یہ تصویر دیکھتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

یہاں کیا ہو رہا ہے؟ پہلی نظر میں، فنکشن کا آغاز اور اختتام ARM فن تعمیر کے لیے مخصوص ہے۔ اسٹیک پر پہلی ہدایات رجسٹروں کے مواد کو ذخیرہ کرتی ہے جسے فنکشن اپنے آپریشن میں استعمال کرے گا (اس صورت میں، R0، R1 اور R2)، ساتھ ہی LR رجسٹر کے مواد، جس میں فنکشن سے واپسی کا پتہ ہوتا ہے۔ . آخری ہدایت محفوظ شدہ رجسٹروں کو بحال کرتی ہے، اور واپسی کا پتہ فوری طور پر PC رجسٹر میں رکھا جاتا ہے - اس طرح فنکشن سے واپسی ہوتی ہے۔ لیکن اگر آپ قریب سے دیکھیں تو آپ دیکھیں گے کہ آخری ہدایات اسٹیک پر ذخیرہ شدہ واپسی کے پتے کو تبدیل کرتی ہیں۔ آئیے حساب لگاتے ہیں کہ اس کے بعد کیسا ہوگا۔
کوڈ پر عمل درآمد. ایک مخصوص ایڈریس 1xB0 کو R130 میں لوڈ کیا جاتا ہے، اس سے 5 کو منہا کیا جاتا ہے، پھر اسے R0 میں منتقل کیا جاتا ہے اور اس میں 0x10 شامل کیا جاتا ہے۔ یہ 0xB13B باہر کر دیتا ہے. اس طرح، IDA سمجھتا ہے کہ آخری ہدایت ایک عام فنکشن کی واپسی ہے، لیکن حقیقت میں یہ حساب شدہ ایڈریس 0xB13B پر جا رہی ہے۔

یہاں یہ یاد رکھنے کے قابل ہے کہ اے آر ایم پروسیسرز کے دو موڈ اور ہدایات کے دو سیٹ ہوتے ہیں: اے آر ایم اور انگوٹھا۔ ایڈریس کا سب سے کم اہم حصہ پروسیسر کو بتاتا ہے کہ کون سا انسٹرکشن سیٹ استعمال کیا جا رہا ہے۔ یعنی پتہ درحقیقت 0xB13A ہے، اور کم از کم اہم بٹ میں سے ایک انگوٹھا موڈ کی نشاندہی کرتا ہے۔

اس لائبریری میں ہر فنکشن کے آغاز میں اسی طرح کا ایک "اڈاپٹر" شامل کیا گیا ہے۔
کوڑا کرکٹ کوڈ ہم ان پر مزید تفصیل سے غور نہیں کریں گے - ہمیں صرف یاد ہے۔
کہ تقریباً تمام فنکشنز کی اصل شروعات تھوڑی دور ہے۔

چونکہ کوڈ واضح طور پر 0xB13A پر نہیں آتا، اس لیے IDA نے خود تسلیم نہیں کیا کہ کوڈ اس مقام پر موجود تھا۔ اسی وجہ سے، یہ لائبریری میں زیادہ تر کوڈ کو کوڈ کے طور پر نہیں پہچانتا، جس کی وجہ سے تجزیہ کچھ مشکل ہو جاتا ہے۔ ہم IDA کو بتاتے ہیں کہ یہ کوڈ ہے، اور ایسا ہوتا ہے:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ٹیبل واضح طور پر 0xB144 سے شروع ہوتا ہے۔ sub_494C میں کیا ہے؟

UC براؤزر میں کمزوریوں کی تلاش ہے۔

LR رجسٹر میں اس فنکشن کو کال کرنے پر، ہمیں پہلے ذکر کردہ ٹیبل (0xB144) کا پتہ ملتا ہے۔ R0 میں - اس ٹیبل میں انڈیکس۔ یعنی ٹیبل سے قدر لی جاتی ہے، LR میں شامل کی جاتی ہے اور نتیجہ نکلتا ہے۔
جانے کا پتہ۔ آئیے اس کا حساب لگانے کی کوشش کرتے ہیں: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264۔ ہم موصولہ ایڈریس پر جاتے ہیں اور لفظی طور پر کچھ مفید ہدایات دیکھتے ہیں اور دوبارہ 0xB140 پر جاتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اب ٹیبل سے انڈیکس 0x20 کے ساتھ آفسیٹ پر ایک منتقلی ہوگی۔

ٹیبل کے سائز کو دیکھتے ہوئے، کوڈ میں ایسی بہت سی تبدیلیاں ہوں گی۔ سوال یہ پیدا ہوتا ہے کہ کیا دستی طور پر پتوں کا حساب لگائے بغیر کسی طرح خود بخود اس سے نمٹنا ممکن ہے۔ اور اسکرپٹس اور IDA میں کوڈ کو پیچ کرنے کی صلاحیت ہماری مدد کے لیے آتی ہے:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 2
if get_wide_word(ea1) == 0xbf00: #NOP
ea1 += 2
if get_operand_type(ea1, 0) == 1 and get_operand_value(ea1, 0) == 0 and get_operand_type(ea1, 1) == 2:
index = get_wide_dword(get_operand_value(ea1, 1))
print "index =", hex(index)
ea1 += 2
if get_operand_type(ea1, 0) == 7:
table = get_operand_value(ea1, 0) + 4
elif get_operand_type(ea1, 1) == 2:
table = get_operand_value(ea1, 1) + 4
else:
print "Wrong operand type on", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
table = None
if table is None:
print "Unable to find table"
else:
print "table =", hex(table)
offset = get_wide_dword(table + (index << 2))
put_unconditional_branch(ea, table + offset)
else:
print "Unknown code", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
print "Unable to detect first instruction"

کرسر کو لائن 0xB26A پر رکھیں، اسکرپٹ چلائیں اور 0xB4B0 میں منتقلی دیکھیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

IDA نے دوبارہ اس علاقے کو کوڈ کے طور پر تسلیم نہیں کیا۔ ہم اس کی مدد کرتے ہیں اور وہاں ایک اور ڈیزائن دیکھتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

BLX کے بعد کی ہدایات زیادہ معنی خیز نہیں لگتی ہیں، یہ کسی طرح کی نقل مکانی کی طرح ہے۔ آئیے ذیلی_4964 کو دیکھتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اور درحقیقت، یہاں LR میں پڑے ایڈریس پر ایک dword لیا جاتا ہے، اس ایڈریس میں شامل کیا جاتا ہے، جس کے بعد نتیجے میں موجود ایڈریس پر ویلیو لے کر اسٹیک پر ڈال دیا جاتا ہے۔ نیز، 4 کو LR میں شامل کیا جاتا ہے تاکہ فنکشن سے واپس آنے کے بعد، اسی آفسیٹ کو چھوڑ دیا جائے۔ جس کے بعد POP {R1} کمانڈ اسٹیک سے نتیجے کی قدر لیتی ہے۔ اگر آپ دیکھیں کہ ایڈریس 0xB4BA + 0xEA = 0xB5A4 پر کیا ہے، تو آپ کو ایڈریس ٹیبل کی طرح کچھ نظر آئے گا:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اس ڈیزائن کو پیچ کرنے کے لیے، آپ کو کوڈ سے دو پیرامیٹرز حاصل کرنے ہوں گے: آفسیٹ اور رجسٹر نمبر جس میں آپ نتیجہ ڈالنا چاہتے ہیں۔ ہر ممکنہ رجسٹر کے لیے، آپ کو کوڈ کا ایک ٹکڑا پہلے سے تیار کرنا ہوگا۔

patches = {}
patches[0] = (0x00, 0xbf, 0x01, 0x48, 0x00, 0x68, 0x02, 0xe0)
patches[1] = (0x00, 0xbf, 0x01, 0x49, 0x09, 0x68, 0x02, 0xe0)
patches[2] = (0x00, 0xbf, 0x01, 0x4a, 0x12, 0x68, 0x02, 0xe0)
patches[3] = (0x00, 0xbf, 0x01, 0x4b, 0x1b, 0x68, 0x02, 0xe0)
patches[4] = (0x00, 0xbf, 0x01, 0x4c, 0x24, 0x68, 0x02, 0xe0)
patches[5] = (0x00, 0xbf, 0x01, 0x4d, 0x2d, 0x68, 0x02, 0xe0)
patches[8] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x80, 0xd8, 0xf8, 0x00, 0x80, 0x01, 0xe0)
patches[9] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x90, 0xd9, 0xf8, 0x00, 0x90, 0x01, 0xe0)
patches[10] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xa0, 0xda, 0xf8, 0x00, 0xa0, 0x01, 0xe0)
patches[11] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xb0, 0xdb, 0xf8, 0x00, 0xb0, 0x01, 0xe0)
ea = here()
if (get_wide_word(ea) == 0xb082 #SUB SP, SP, #8
and get_wide_word(ea + 2) == 0xb503): #PUSH {R0,R1,LR}
if get_operand_type(ea + 4, 0) == 7:
pop = get_bytes(ea + 12, 4, 0)
if pop[1] == 'xbc':
register = -1
r = get_wide_byte(ea + 12)
for i in range(8):
if r == (1 << i):
register = i
break
if register == -1:
print "Unable to detect register"
else:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
if ea % 4 != 0:
ea += 2
patch_dword(ea, address)
elif pop[:3] == 'x5dxf8x04':
register = ord(pop[3]) >> 4
if register in patches:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
patch_dword(ea, address)
else:
print "POP instruction not found"
else:
print "Wrong operand type on +4:", get_operand_type(ea + 4, 0)
else:
print "Unable to detect first instructions"

ہم کرسر کو اس ڈھانچے کے شروع میں رکھتے ہیں جسے ہم تبدیل کرنا چاہتے ہیں - 0xB4B2 - اور اسکرپٹ کو چلاتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

پہلے سے ذکر کردہ ڈھانچے کے علاوہ، کوڈ میں درج ذیل چیزیں بھی شامل ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

جیسا کہ پچھلے کیس میں، BLX ہدایات کے بعد ایک آفسیٹ ہے:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ہم آفسیٹ کو ایل آر سے ایڈریس پر لے جاتے ہیں، اسے ایل آر میں شامل کرتے ہیں اور وہاں جاتے ہیں۔ 0x72044 + 0xC = 0x72050۔ اس ڈیزائن کے لیے اسکرپٹ کافی آسان ہے:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 6
if get_wide_word(ea + 2) == 0xbf00: #NOP
ea1 += 2
offset = get_wide_dword(ea1)
put_unconditional_branch(ea, (ea1 + offset) & 0xffffffff)
else:
print "Unable to detect first instruction"

اسکرپٹ پر عمل درآمد کا نتیجہ:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ایک بار جب فنکشن میں سب کچھ ٹھیک ہوجاتا ہے، آپ IDA کو اس کی اصل شروعات کی طرف اشارہ کرسکتے ہیں۔ یہ تمام فنکشن کوڈ کو اکٹھا کر دے گا، اور اسے HexRays کا استعمال کرتے ہوئے ڈی کمپائل کیا جا سکتا ہے۔

تاروں کو ضابطہ کشائی کرنا

ہم نے لائبریری میں مشین کوڈ کے مبہم ہونے سے نمٹنا سیکھ لیا ہے۔ libsgmainso-6.4.36.so UC براؤزر سے اور فنکشن کوڈ موصول ہوا۔ JNI_Onload.

int __fastcall real_JNI_OnLoad(JavaVM *vm)
{
int result; // r0
jclass clazz; // r0 MAPDST
int v4; // r0
JNIEnv *env; // r4
int v6; // [sp-40h] [bp-5Ch]
int v7; // [sp+Ch] [bp-10h]
v7 = *(_DWORD *)off_8AC00;
if ( !vm )
goto LABEL_39;
sub_7C4F4();
env = (JNIEnv *)sub_7C5B0(0);
if ( !env )
goto LABEL_39;
v4 = sub_72CCC();
sub_73634(v4);
sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
if ( clazz
&& (sub_9EE4(),
sub_71D68(env),
sub_E7DC(env) >= 0
&& sub_69D68(env) >= 0
&& sub_197B4(env, clazz) >= 0
&& sub_E240(env, clazz) >= 0
&& sub_B8B0(env, clazz) >= 0
&& sub_5F0F4(env, clazz) >= 0
&& sub_70640(env, clazz) >= 0
&& sub_11F3C(env) >= 0
&& sub_21C3C(env, clazz) >= 0
&& sub_2148C(env, clazz) >= 0
&& sub_210E0(env, clazz) >= 0
&& sub_41B58(env, clazz) >= 0
&& sub_27920(env, clazz) >= 0
&& sub_293E8(env, clazz) >= 0
&& sub_208F4(env, clazz) >= 0) )
{
result = (sub_B7B0(env, clazz) >> 31) | 0x10004;
}
else
{
LABEL_39:
result = -1;
}
return result;
}

آئیے درج ذیل سطروں پر گہری نظر ڈالتے ہیں۔

  sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);

فنکشن میں sub_73E24 کلاس کا نام واضح طور پر ڈکرپٹ کیا جا رہا ہے۔ اس فنکشن کے پیرامیٹرز کے طور پر، انکرپٹڈ ڈیٹا کی طرح ڈیٹا کی طرف ایک پوائنٹر، ایک مخصوص بفر اور ایک نمبر پاس کیا جاتا ہے۔ ظاہر ہے، فنکشن کو کال کرنے کے بعد، بفر میں ایک ڈکرپٹڈ لائن ہوگی، کیونکہ اسے فنکشن میں منتقل کیا جاتا ہے۔ فائنڈ کلاس، جو کلاس کا نام دوسرے پیرامیٹر کے طور پر لیتا ہے۔ لہذا، نمبر بفر کا سائز یا لائن کی لمبائی ہے. آئیے کلاس کے نام کو سمجھنے کی کوشش کرتے ہیں، یہ ہمیں بتانا چاہیے کہ کیا ہم صحیح سمت میں جا رہے ہیں۔ آئیے اس پر گہری نظر ڈالتے ہیں کہ میں کیا ہوتا ہے۔ sub_73E24.

int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size)
{
int v4; // r6
int v7; // r11
int v8; // r9
int v9; // r4
size_t v10; // r5
int v11; // r0
struc_1 v13; // [sp+0h] [bp-30h]
int v14; // [sp+1Ch] [bp-14h]
int v15; // [sp+20h] [bp-10h]
v4 = 0;
v15 = *(_DWORD *)off_8AC00;
v14 = 0;
v7 = sub_7AF78(17);
v8 = sub_7AF78(size);
if ( !v7 )
{
v9 = 0;
goto LABEL_12;
}
(*(void (__fastcall **)(int, const char *, int))(v7 + 12))(v7, "DcO/lcK+h?m3c*q@", 16);
if ( !v8 )
{
LABEL_9:
v4 = 0;
goto LABEL_10;
}
v4 = 0;
if ( !in )
{
LABEL_10:
v9 = 0;
goto LABEL_11;
}
v9 = 0;
if ( out )
{
memset(out, 0, size);
v10 = size - 1;
(*(void (__fastcall **)(int, unsigned __int8 *, size_t))(v8 + 12))(v8, in, v10);
memset(&v13, 0, 0x14u);
v13.field_4 = 3;
v13.field_10 = v7;
v13.field_14 = v8;
v11 = sub_6115C(&v13, &v14);
v9 = v11;
if ( v11 )
{
if ( *(_DWORD *)(v11 + 4) == v10 )
{
qmemcpy(out, *(const void **)v11, v10);
v4 = *(_DWORD *)(v9 + 4);
}
else
{
v4 = 0;
}
goto LABEL_11;
}
goto LABEL_9;
}
LABEL_11:
sub_7B148(v7);
LABEL_12:
if ( v8 )
sub_7B148(v8);
if ( v9 )
sub_7B148(v9);
return v4;
}

فنکشن sub_7AF78 مخصوص سائز کے بائٹ اریوں کے لیے ایک کنٹینر کی مثال بناتا ہے (ہم ان کنٹینرز پر تفصیل سے غور نہیں کریں گے)۔ یہاں دو ایسے کنٹینرز بنائے گئے ہیں: ایک لائن پر مشتمل ہے۔ "DcO/lcK+h?m3c*q@" (یہ اندازہ لگانا آسان ہے کہ یہ ایک کلید ہے)، دوسرے میں انکرپٹڈ ڈیٹا ہوتا ہے۔ اگلا، دونوں اشیاء کو ایک خاص ڈھانچے میں رکھا جاتا ہے، جو فنکشن میں منتقل ہوتا ہے۔ sub_6115C. آئیے اس ڈھانچے میں ویلیو 3 کے ساتھ ایک فیلڈ کو بھی نشان زد کرتے ہیں۔ آئیے دیکھتے ہیں کہ اس ڈھانچے کا آگے کیا ہوتا ہے۔

int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2)
{
int v3; // lr
unsigned int v4; // r1
int v5; // r0
int v6; // r1
int result; // r0
int v8; // r0
*a2 = 820000;
if ( a1 )
{
v3 = a1->field_14;
if ( v3 )
{
v4 = a1->field_4;
if ( v4 < 0x19 )
{
switch ( v4 )
{
case 0u:
v8 = sub_6419C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 3u:
v8 = sub_6364C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 0x10u:
case 0x11u:
case 0x12u:
v8 = sub_612F4(
a1->field_0,
v4,
*(_QWORD *)&a1->field_8,
*(_QWORD *)&a1->field_8 >> 32,
a1->field_10,
v3,
a2);
goto LABEL_17;
case 0x14u:
v8 = sub_63A28(a1->field_0, v3);
goto LABEL_17;
case 0x15u:
sub_61A60(a1->field_0, v3, a2);
return result;
case 0x16u:
v8 = sub_62440(a1->field_14);
goto LABEL_17;
case 0x17u:
v8 = sub_6226C(a1->field_10, v3);
goto LABEL_17;
case 0x18u:
v8 = sub_63530(a1->field_14);
LABEL_17:
v6 = 0;
if ( v8 )
{
*a2 = 0;
v6 = v8;
}
return v6;
default:
LOWORD(v5) = 28032;
goto LABEL_5;
}
}
}
}
LOWORD(v5) = -27504;
LABEL_5:
HIWORD(v5) = 13;
v6 = 0;
*a2 = v5;
return v6;
}

سوئچ پیرامیٹر ایک سٹرکچر فیلڈ ہے جسے پہلے ویلیو 3 تفویض کیا گیا تھا۔ کیس 3 دیکھیں: فنکشن کو sub_6364C پیرامیٹرز اس ڈھانچے سے پاس کیے جاتے ہیں جو پچھلے فنکشن میں شامل کیے گئے تھے، یعنی کلید اور خفیہ کردہ ڈیٹا۔ اگر آپ غور سے دیکھیں sub_6364C، آپ اس میں RC4 الگورتھم کو پہچان سکتے ہیں۔

ہمارے پاس الگورتھم اور کلید ہے۔ آئیے کلاس کے نام کو سمجھنے کی کوشش کرتے ہیں۔ یہاں کیا ہوا ہے: com/taobao/wireless/security/adapter/JNICLlibrary. زبردست! ہم صحیح راستے پر ہیں۔

حکم درخت

اب ہمیں ایک چیلنج تلاش کرنے کی ضرورت ہے۔ RegisterNatives، جو ہمیں فنکشن کی طرف اشارہ کرے گا۔ doCommandNative. آئیے فنکشنز کو دیکھتے ہیں جن سے کہا جاتا ہے۔ JNI_Onload, اور ہم اسے تلاش کرتے ہیں۔ ذیلی_B7B0:

int __fastcall sub_B7F6(JNIEnv *env, jclass clazz)
{
char signature[41]; // [sp+7h] [bp-55h]
char name[16]; // [sp+30h] [bp-2Ch]
JNINativeMethod method; // [sp+40h] [bp-1Ch]
int v8; // [sp+4Ch] [bp-10h]
v8 = *(_DWORD *)off_8AC00;
decryptString((unsigned __int8 *)&unk_83ED9, (unsigned __int8 *)name, 0x10u);// doCommandNative
decryptString((unsigned __int8 *)&unk_83EEA, (unsigned __int8 *)signature, 0x29u);// (I[Ljava/lang/Object;)Ljava/lang/Object;
method.name = name;
method.signature = signature;
method.fnPtr = sub_B69C;
return ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, int))(*env)->RegisterNatives)(env, clazz, &method, 1) >> 31;
}

اور درحقیقت، نام کے ساتھ ایک مقامی طریقہ یہاں رجسٹرڈ ہے۔ doCommandNative. اب ہم اس کا پتہ جانتے ہیں۔ آئیے دیکھتے ہیں کہ وہ کیا کرتا ہے۔

int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args)
{
int v5; // r5
struc_2 *a5; // r6
int v9; // r1
int v11; // [sp+Ch] [bp-14h]
int v12; // [sp+10h] [bp-10h]
v5 = 0;
v12 = *(_DWORD *)off_8AC00;
v11 = 0;
a5 = (struc_2 *)malloc(0x14u);
if ( a5 )
{
a5->field_0 = 0;
a5->field_4 = 0;
a5->field_8 = 0;
a5->field_C = 0;
v9 = command % 10000 / 100;
a5->field_0 = command / 10000;
a5->field_4 = v9;
a5->field_8 = command % 100;
a5->field_C = env;
a5->field_10 = args;
v5 = sub_9D60(command / 10000, v9, command % 100, 1, (int)a5, &v11);
}
free(a5);
if ( !v5 && v11 )
sub_7CF34(env, v11, &byte_83ED7);
return v5;
}

نام سے آپ اندازہ لگا سکتے ہیں کہ یہاں ان تمام فنکشنز کا انٹری پوائنٹ ہے جنہیں ڈویلپرز نے مقامی لائبریری میں منتقل کرنے کا فیصلہ کیا ہے۔ ہم فنکشن نمبر 10601 میں دلچسپی رکھتے ہیں۔

آپ کوڈ سے دیکھ سکتے ہیں کہ کمانڈ نمبر تین نمبر تیار کرتا ہے: کمانڈ/10000, کمانڈ % 10000 / 100 и کمانڈ %10، یعنی، ہمارے معاملے میں، 1، 6 اور 1۔ یہ تینوں نمبروں کے ساتھ ساتھ ایک پوائنٹر JNIEnv اور فنکشن میں بھیجے گئے دلائل کو ایک ڈھانچے میں شامل کر کے آگے بڑھایا جاتا ہے۔ حاصل کردہ تین نمبروں کا استعمال کرتے ہوئے (آئیے ان کو N1، N2 اور N3 کی نشاندہی کریں)، ایک کمانڈ ٹری بنایا گیا ہے۔

کچھ اس طرح:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

درخت متحرک طور پر بھرا ہوا ہے۔ JNI_Onload.
تین نمبر درخت میں راستے کو انکوڈ کرتے ہیں۔ درخت کے ہر پتے میں متعلقہ فنکشن کا پوک ایڈریس ہوتا ہے۔ کلید پیرنٹ نوڈ میں ہے۔ کوڈ میں اس جگہ کو تلاش کرنا جہاں ہمیں درکار فنکشن کو درخت میں شامل کیا گیا ہے اگر آپ استعمال شدہ تمام ڈھانچے کو سمجھتے ہیں تو مشکل نہیں ہے (ہم ان کی وضاحت نہیں کرتے ہیں تاکہ پہلے سے ہی بڑے مضمون کو پھولا نہ جائے)۔

مزید الجھن

ہمیں اس فنکشن کا پتہ موصول ہوا جو ٹریفک کو ڈیکرپٹ کرے: 0x5F1AC۔ لیکن خوشی منانا بہت جلد ہے: UC براؤزر کے ڈویلپرز نے ہمارے لیے ایک اور سرپرائز تیار کیا ہے۔

جاوا کوڈ میں بننے والی صف سے پیرامیٹرز حاصل کرنے کے بعد، ہمیں ملتا ہے۔
ایڈریس 0x4D070 پر فنکشن کے لیے۔ اور یہاں ایک اور قسم کا کوڈ ابوبسیکشن ہمارا انتظار کر رہا ہے۔

ہم R7 اور R4 میں دو انڈیکس ڈالتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ہم پہلے انڈیکس کو R11 پر منتقل کرتے ہیں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ٹیبل سے ایڈریس حاصل کرنے کے لیے، انڈیکس استعمال کریں:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

پہلے ایڈریس پر جانے کے بعد دوسرا انڈیکس استعمال ہوتا ہے جو کہ R4 میں ہوتا ہے۔ جدول میں 230 عناصر ہیں۔

اس کے بارے میں کیا کرنا ہے؟ آپ IDA کو بتا سکتے ہیں کہ یہ ایک سوئچ ہے: Edit -> Other -> Specify switch idiom۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

نتیجہ خیز کوڈ خوفناک ہے۔ لیکن، اس کے جنگل میں اپنا راستہ بناتے ہوئے، آپ ایک فنکشن کو کال محسوس کر سکتے ہیں جو ہم سے پہلے سے واقف ہے۔ sub_6115C:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ایک سوئچ تھا جس میں کیس 3 میں RC4 الگورتھم کا استعمال کرتے ہوئے ایک ڈکرپشن تھا۔ اور اس صورت میں، فنکشن میں پاس کردہ ڈھانچہ کو پاس کیے گئے پیرامیٹرز سے بھرا جاتا ہے۔ doCommandNative. آئیے یاد رکھیں کہ ہمارے پاس وہاں کیا تھا۔ magicInt قدر کے ساتھ 16۔ ہم متعلقہ کیس کو دیکھتے ہیں - اور کئی ٹرانزیشن کے بعد ہمیں وہ کوڈ ملتا ہے جس کے ذریعے الگورتھم کی شناخت کی جا سکتی ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

یہ AES ہے!

الگورتھم موجود ہے، اس کے پیرامیٹرز کو حاصل کرنا باقی ہے: موڈ، کلید اور ممکنہ طور پر، ابتدائی ویکٹر (اس کی موجودگی AES الگورتھم کے آپریٹنگ موڈ پر منحصر ہے)۔ ان کے ساتھ ڈھانچہ فنکشن کال سے پہلے کہیں بننا ضروری ہے۔ sub_6115C، لیکن کوڈ کا یہ حصہ خاص طور پر اچھی طرح سے مبہم ہے، لہذا کوڈ کو پیچ کرنے کا خیال پیدا ہوتا ہے تاکہ ڈکرپشن فنکشن کے تمام پیرامیٹرز کو فائل میں پھینک دیا جائے۔

پیچ

تمام پیچ کوڈ کو اسمبلی لینگویج میں دستی طور پر نہ لکھنے کے لیے، آپ اینڈرائیڈ اسٹوڈیو کو لانچ کر سکتے ہیں، وہاں ایک فنکشن لکھ سکتے ہیں جو ہمارے ڈکرپشن فنکشن کی طرح ان پٹ پیرامیٹرز حاصل کرتا ہے اور فائل میں لکھتا ہے، پھر اس کوڈ کو کاپی پیسٹ کر سکتا ہے جسے کمپائلر کرے گا۔ پیدا کرنا

UC براؤزر ٹیم کے ہمارے دوستوں نے بھی کوڈ شامل کرنے کی سہولت کا خیال رکھا۔ یاد رکھیں کہ ہر فنکشن کے شروع میں ہمارے پاس کوڑے کا کوڈ ہوتا ہے جسے آسانی سے کسی دوسرے سے تبدیل کیا جا سکتا ہے۔ بہت آسان 🙂 تاہم، ٹارگٹ فنکشن کے آغاز میں اس کوڈ کے لیے کافی جگہ نہیں ہے جو تمام پیرامیٹرز کو فائل میں محفوظ کر دے۔ مجھے اسے حصوں میں تقسیم کرنا پڑا اور پڑوسی افعال سے کوڑے کے بلاکس کا استعمال کرنا پڑا۔ کل چار حصے تھے۔

پہلا حصہ:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اے آر ایم آرکیٹیکچر میں، پہلے چار فنکشن پیرامیٹرز رجسٹر R0-R3 سے گزرے ہیں، باقی، اگر کوئی ہے، اسٹیک سے گزرے ہیں۔ LR رجسٹر میں واپسی کا پتہ ہوتا ہے۔ اس سب کو محفوظ کرنے کی ضرورت ہے تاکہ فنکشن کام کر سکے جب ہم اس کے پیرامیٹرز کو پھینک دیتے ہیں۔ ہمیں ان تمام رجسٹروں کو بھی محفوظ کرنے کی ضرورت ہے جو ہم اس عمل میں استعمال کریں گے، اس لیے ہم PUSH.W {R0-R10,LR} کرتے ہیں۔ R7 میں ہمیں اسٹیک کے ذریعے فنکشن میں بھیجے گئے پیرامیٹرز کی فہرست کا پتہ ملتا ہے۔

فنکشن کا استعمال کرتے ہوئے fopen آئیے فائل کو کھولتے ہیں۔ /data/local/tmp/aes "ab" موڈ میں
یعنی اضافے کے لیے۔ R0 میں ہم فائل کے نام کا پتہ لوڈ کرتے ہیں، R1 میں - لائن کا پتہ جو موڈ کی نشاندہی کرتا ہے۔ اور یہاں ردی کی ٹوکری کا کوڈ ختم ہوتا ہے، لہذا ہم اگلے فنکشن کی طرف بڑھتے ہیں۔ اس کے کام کو جاری رکھنے کے لیے، ہم شروع میں ردی کی ٹوکری کو نظرانداز کرتے ہوئے فنکشن کے حقیقی کوڈ میں منتقلی ڈالتے ہیں، اور کوڑے کے بجائے ہم پیچ کا تسلسل شامل کرتے ہیں۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

کال کرنا fopen.

فنکشن کے پہلے تین پیرامیٹرز یئایس قسم ہے int. چونکہ ہم نے شروع میں رجسٹر کو اسٹیک میں محفوظ کیا ہے، اس لیے ہم آسانی سے فنکشن پاس کر سکتے ہیں۔ لکھنا اسٹیک پر ان کے پتے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اس کے بعد ہمارے پاس تین ڈھانچے ہیں جن میں ڈیٹا کا سائز اور کلید، ابتدائی ویکٹر اور انکرپٹڈ ڈیٹا کے لیے ڈیٹا کی طرف اشارہ ہے۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

آخر میں، فائل کو بند کریں، رجسٹر کو بحال کریں اور کنٹرول کو حقیقی فنکشن میں منتقل کریں۔ یئایس.

ہم پیچ شدہ لائبریری کے ساتھ ایک APK جمع کرتے ہیں، اس پر دستخط کرتے ہیں، اسے ڈیوائس/ایمولیٹر پر اپ لوڈ کرتے ہیں، اور اسے لانچ کرتے ہیں۔ ہم دیکھتے ہیں کہ ہمارا ڈمپ بنایا جا رہا ہے، اور وہاں بہت سا ڈیٹا لکھا جا رہا ہے۔ براؤزر نہ صرف ٹریفک کے لیے انکرپشن کا استعمال کرتا ہے، اور تمام انکرپشن زیر بحث فنکشن سے گزرتی ہے۔ لیکن کسی وجہ سے ضروری ڈیٹا موجود نہیں ہے، اور مطلوبہ درخواست ٹریفک میں نظر نہیں آ رہی ہے۔ UC براؤزر کی جانب سے ضروری درخواست کرنے کا انتظار نہ کرنے کے لیے، آئیے پہلے موصول ہونے والے سرور سے انکرپٹ شدہ جواب لیں اور ایپلیکیشن کو دوبارہ پیچ کریں: مرکزی سرگرمی کے onCreate میں ڈکرپشن شامل کریں۔

    const/16 v1, 0x62
new-array v1, v1, [B
fill-array-data v1, :encrypted_data
const/16 v0, 0x1f
invoke-static {v0, v1}, Lcom/uc/browser/core/d/c/g;->j(I[B)[B
move-result-object v1
array-length v2, v1
invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v2
const-string v0, "ololo"
invoke-static {v0, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

ہم جمع کرتے ہیں، دستخط کرتے ہیں، انسٹال کرتے ہیں، لانچ کرتے ہیں۔ ہمیں ایک NullPointerException موصول ہوتا ہے کیونکہ طریقہ null واپس آیا۔

کوڈ کے مزید تجزیہ کے دوران، ایک فنکشن دریافت ہوا جو دلچسپ لائنوں کو سمجھتا ہے: "META-INF/" اور ".RSA"۔ ایسا لگتا ہے کہ درخواست اپنے سرٹیفکیٹ کی تصدیق کر رہی ہے۔ یا اس سے چابیاں بھی تیار کرتا ہے۔ میں واقعی میں سرٹیفکیٹ کے ساتھ جو کچھ ہو رہا ہے اس سے نمٹنا نہیں چاہتا، لہذا ہم اسے صحیح سرٹیفکیٹ پرچی کریں گے۔ آئیے انکرپٹڈ لائن کو پیچ کرتے ہیں تاکہ "META-INF/" کے بجائے ہمیں "BLABLINF/" ملے، APK میں اس نام کے ساتھ ایک فولڈر بنائیں اور وہاں گلہری براؤزر سرٹیفکیٹ شامل کریں۔

ہم جمع کرتے ہیں، دستخط کرتے ہیں، انسٹال کرتے ہیں، لانچ کرتے ہیں۔ بنگو! ہمارے پاس چابی ہے!

MitM

ہمیں کلید کے برابر ایک کلید اور ایک ابتدائی ویکٹر ملا۔ آئیے سی بی سی موڈ میں سرور کے جواب کو ڈکرپٹ کرنے کی کوشش کرتے ہیں۔

UC براؤزر میں کمزوریوں کی تلاش ہے۔

ہم آرکائیو یو آر ایل دیکھتے ہیں، جو MD5 سے ملتا جلتا ہے، "extract_unzipsize" اور ایک نمبر۔ ہم چیک کرتے ہیں: آرکائیو کا MD5 ایک جیسا ہے، غیر پیک شدہ لائبریری کا سائز ایک جیسا ہے۔ ہم اس لائبریری کو پیچ کرنے اور براؤزر کو دینے کی کوشش کر رہے ہیں۔ یہ دکھانے کے لیے کہ ہماری پیچ شدہ لائبریری لوڈ ہو گئی ہے، ہم "PWNED!" متن کے ساتھ ایک SMS بنانے کا ارادہ شروع کریں گے۔ ہم سرور سے دو جوابات بدل دیں گے: puds.ucweb.com/upgrade/index.xhtml اور محفوظ شدہ دستاویزات کو ڈاؤن لوڈ کرنے کے لیے۔ پہلے ہم MD5 کو تبدیل کرتے ہیں (پیک کھولنے کے بعد سائز تبدیل نہیں ہوتا ہے)، دوسرے میں ہم آرکائیو کو پیچ شدہ لائبریری کے ساتھ دیتے ہیں۔

براؤزر آرکائیو کو کئی بار ڈاؤن لوڈ کرنے کی کوشش کرتا ہے، جس کے بعد یہ ایک ایرر دیتا ہے۔ بظاہر کچھ
وہ پسند نہیں کرتا. اس گھمبیر شکل کا تجزیہ کرنے کے نتیجے میں، یہ پتہ چلا کہ سرور آرکائیو کے سائز کو بھی منتقل کرتا ہے:

UC براؤزر میں کمزوریوں کی تلاش ہے۔

اسے LEB128 میں انکوڈ کیا گیا ہے۔ پیچ کے بعد، لائبریری کے ساتھ آرکائیو کا سائز تھوڑا سا بدل گیا، اس لیے براؤزر نے سمجھا کہ آرکائیو ٹیڑھے طریقے سے ڈاؤن لوڈ کیا گیا تھا، اور کئی کوششوں کے بعد اس میں غلطی ہو گئی۔

ہم محفوظ شدہ دستاویزات کے سائز کو ایڈجسٹ کرتے ہیں... اور - فتح! 🙂 نتیجہ ویڈیو میں ہے۔

https://www.youtube.com/watch?v=Nfns7uH03J8

نتائج اور ڈویلپر کا ردعمل

اسی طرح، ہیکرز UC براؤزر کی غیر محفوظ خصوصیت کو نقصاندہ لائبریریوں کو تقسیم کرنے اور چلانے کے لیے استعمال کر سکتے ہیں۔ یہ لائبریریاں براؤزر کے تناظر میں کام کریں گی، اس لیے انہیں اس کے سسٹم کی تمام اجازتیں مل جائیں گی۔ نتیجے کے طور پر، فشنگ ونڈوز کو ظاہر کرنے کی صلاحیت کے ساتھ ساتھ نارنجی چینی گلہری کی ورکنگ فائلوں تک رسائی، بشمول لاگ ان، پاس ورڈز اور ڈیٹا بیس میں محفوظ کوکیز۔

ہم نے UC براؤزر کے ڈویلپرز سے رابطہ کیا اور انہیں جو مسئلہ پایا اس سے آگاہ کیا، کمزوری اور اس کے خطرے کی نشاندہی کرنے کی کوشش کی، لیکن انہوں نے ہم سے کوئی بات نہیں کی۔ دریں اثنا، براؤزر اپنی خطرناک خصوصیت کو صاف نظر میں دکھاتا رہا۔ لیکن ایک بار جب ہم نے خطرے کی تفصیلات کا انکشاف کیا تو اسے پہلے کی طرح نظر انداز کرنا ممکن نہیں رہا۔ 27 مارچ تھا۔
UC براؤزر 12.10.9.1193 کا نیا ورژن جاری کیا گیا، جس نے HTTPS کے ذریعے سرور تک رسائی حاصل کی: puds.ucweb.com/upgrade/index.xhtml.

اس کے علاوہ، "ٹھیک" کے بعد اور اس مضمون کو لکھنے کے وقت تک، براؤزر میں پی ڈی ایف کھولنے کی کوشش کے نتیجے میں "افوہ، کچھ غلط ہو گیا!" متن کے ساتھ ایک غلطی کا پیغام آیا۔ پی ڈی ایف کھولنے کی کوشش کرتے وقت سرور سے درخواست نہیں کی گئی تھی، لیکن براؤزر کے لانچ ہونے پر ایک درخواست کی گئی تھی، جو گوگل پلے کے قوانین کی خلاف ورزی کرتے ہوئے قابل عمل کوڈ کو ڈاؤن لوڈ کرنے کی مسلسل صلاحیت کی طرف اشارہ کرتی ہے۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں