UC Browser-ийн эмзэг байдлыг хайж байна

UC Browser-ийн эмзэг байдлыг хайж байна

Танилцуулга

Гуравдугаар сарын сүүлээр бид мэдээлэв, тэд UC Browser-д баталгаажуулаагүй кодыг ачаалах, ажиллуулах далд чадварыг олж илрүүлсэн. Өнөөдөр бид энэ таталт хэрхэн явагддаг, хакерууд үүнийг хэрхэн өөрийн зорилгоор ашиглаж болох талаар дэлгэрэнгүй авч үзэх болно.

Хэсэг хугацааны өмнө UC Browser-ийг сурталчилж, маш түрэмгий байдлаар тарааж байсан: энэ нь янз бүрийн сайтуудаас видео файл нэрийн дор тарааж, хортой програм ашиглан хэрэглэгчдийн төхөөрөмжид суулгасан (өөрөөр хэлбэл, хэрэглэгчид порно видео татаж байна гэж бодсон боловч оронд нь энэ хөтөчтэй APK хүлээн авсан), хөтөч нь хуучирсан, эмзэг гэх мэт мессеж бүхий аймшигтай баннеруудыг ашигласан. VK дээрх албан ёсны UC Browser бүлэгт байдаг сэдэв, Хэрэглэгчид шударга бус зар сурталчилгааны талаар гомдоллож болох олон жишээ бий. 2016 онд жигд байсан видео сурталчилгаа орос хэл дээр (тиймээ, зар хориглох хөтөчийг сурталчлах).

Үүнийг бичиж байх үед UC Browser нь Google Play дээр 500 гаруй суулгацтай байна. Энэ нь гайхалтай - зөвхөн Google Chrome-д илүү олон зүйл бий. Шүүмжүүдээс та Google Play дээрх зарим аппликешн руу чиглэсэн зар сурталчилгаа, дахин чиглүүлэлтийн талаар нэлээд олон гомдол гарч ирж байгааг харж болно. Энэ нь бидний судалгааны шалтгаан байсан: бид UC Browser муу зүйл хийж байгаа эсэхийг шалгахаар шийдсэн. Тэгээд тэр тэгдэг нь тодорхой болов!

Програмын кодонд гүйцэтгэх кодыг татаж авах, ажиллуулах чадварыг олж илрүүлсэн. Өргөдөл нийтлэх журамтай зөрчилдөж байна Google Play дээр. Гүйцэтгэх кодыг татаж авахаас гадна UC Browser үүнийг найдвартай бус байдлаар хийдэг бөгөөд үүнийг MitM халдлагыг эхлүүлэхэд ашиглаж болно. Ийм дайралт хийж чадах эсэхийг харцгаая.

Доор бичсэн бүх зүйл нь судалгаа хийх үед Google Play дээр байсан UC Browser-ийн хувилбарт хамааралтай болно.

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

Довтолгооны вектор

UC Browser манифестээс та өөрөө тайлбарласан нэртэй үйлчилгээг олох боломжтой com.uc.deployment.UpgradeDeployService.

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

Энэ үйлчилгээ эхлэхэд хөтөч POST хүсэлт илгээдэг puds.ucweb.com/upgrade/index.xhtml, энэ нь эхэлснээс хойш хэсэг хугацааны дараа замын хөдөлгөөнд харагдах болно. Үүний хариуд тэрээр шинэчлэлт эсвэл шинэ модулийг татаж авах тушаалыг хүлээн авч магадгүй юм. Шинжилгээний явцад сервер ийм команд өгөөгүй боловч бид хөтөч дээр PDF файл нээхийг оролдох үед дээр дурдсан хаягаар хоёр дахь хүсэлт гаргаж, дараа нь эх номын санг татаж байгааг анзаарсан. Халдлагыг хэрэгжүүлэхийн тулд бид UC Browser-ийн энэ функцийг ашиглахаар шийдсэн: APK-д байхгүй, шаардлагатай бол интернетээс татаж авдаг уугуул номын санг ашиглан PDF файлыг нээх боломжтой. Онолын хувьд UC Browser нь хэрэглэгчийн оролцоогүйгээр ямар нэгэн зүйлийг татаж авах боломжтой гэдгийг тэмдэглэх нь зүйтэй - хэрэв та хөтчийг ажиллуулсны дараа гүйцэтгэсэн хүсэлтэд сайн хариу өгөх юм бол. Гэхдээ үүнийг хийхийн тулд бид сервертэй харилцах протоколыг илүү нарийвчлан судлах шаардлагатай байгаа тул саатуулсан хариултыг засварлаж, PDF-тэй ажиллах номын санг солих нь илүү хялбар байх болно гэж шийдсэн.

Тиймээс, хэрэглэгч PDF файлыг шууд хөтөч дээр нээхийг хүсвэл замын хөдөлгөөнд дараах хүсэлтийг харж болно.

UC Browser-ийн эмзэг байдлыг хайж байна

Эхлээд POST хүсэлт байна puds.ucweb.com/upgrade/index.xhtml, дараа нь
PDF болон оффисын форматыг үзэх номын сан бүхий архивыг татаж авлаа. Эхний хүсэлт нь системийн талаарх мэдээллийг дамжуулдаг (наад зах нь шаардлагатай номын сангаар хангах архитектур) бөгөөд үүний хариуд хөтөч нь татаж авах шаардлагатай номын сангийн тухай зарим мэдээллийг хүлээн авдаг: хаяг, магадгүй. , Өөр ямар нэг зүйл. Асуудал нь энэ хүсэлтийг шифрлэсэн явдал юм.

Хүсэлтийн фрагмент

Хариултын хэсэг

UC Browser-ийн эмзэг байдлыг хайж байна

UC Browser-ийн эмзэг байдлыг хайж байна

Номын сан нь өөрөө ZIP дээр багцлагдсан бөгөөд шифрлэгдээгүй.

UC Browser-ийн эмзэг байдлыг хайж байна

Замын хөдөлгөөний код тайлах код хайх

Серверийн хариултыг тайлахыг оролдъё. Ангийн кодыг харцгаая 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. Бидэнд хэрэгтэй зүйл шиг сонсогдож байна. Мөрүүдийг ("дээш_шифрлэх" гэх мэт) харвал серверийн хариултыг тайлах аргыг энд дуудах хэрэгтэй.
Арга руугаа явцгаая gj. Эхний аргумент нь офсет 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);
}
}

Бас анги JNICL номын сан, үүнд уугуул аргыг зарласан 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 Browser-ийн эмзэг байдлыг хайж байна

Гэхдээ энэ шаардлагагүй: ELF файлыг зөв ачаалж, дүн шинжилгээ хийхэд програмын толгойн хүснэгт хангалттай. Тиймээс бид зүгээр л хэсгийн хүснэгтийг устгаж, толгой хэсэгт харгалзах талбаруудыг тэглэнэ.

UC Browser-ийн эмзэг байдлыг хайж байна

Файлыг IDA-д дахин нээнэ үү.

Java виртуал машинд Java кодын эх гэж зарласан аргын хэрэгжилтийг эх номын сангийн яг хаана байгааг хэлэх хоёр арга бий. Эхнийх нь төрөл зүйлийн нэр өгөх явдал юм Java_багцын_нэр_АнгийнНэр_АргынНэр.

Хоёр дахь нь номын санг ачаалах үед үүнийг бүртгэх явдал юм (функц дотор JNI_Ачаалалтай)
функцийн дуудлага ашиглан Төрөлх иргэдийг бүртгэх.

Манай тохиолдолд, хэрэв бид эхний аргыг хэрэглэвэл нэр нь дараах байдалтай байх ёстой. Java_com_taobao_wireless_security_adapter_JNICLLibrary_doCommandNative.

Экспортолсон функцуудын дунд ийм функц байхгүй тул та дуудлага хайх хэрэгтэй гэсэн үг юм Төрөлх иргэдийг бүртгэх.
Функц руугаа явцгаая JNI_Ачаалалтай мөн бид энэ зургийг харж байна:

UC Browser-ийн эмзэг байдлыг хайж байна

Энд юу болоод байна? Эхлээд харахад функцын эхлэл ба төгсгөл нь ARM архитектурын хувьд ердийн зүйл юм. Стек дээрх эхний заавар нь функцийн үйл ажиллагаанд ашиглах регистрүүдийн агуулгыг (энэ тохиолдолд R0, R1 ба R2), мөн функцээс буцах хаягийг агуулсан LR регистрийн агуулгыг хадгалдаг. . Сүүлчийн заавар нь хадгалсан регистрүүдийг сэргээж, буцах хаягийг нэн даруй компьютерийн бүртгэлд байрлуулж, функцээс буцаж ирдэг. Гэхдээ хэрэв та анхааралтай ажиглавал эцсийн өмнөх заавар нь стек дээр хадгалагдсан буцах хаягийг өөрчилдөг болохыг анзаарах болно. Дараа нь ямар байхыг тооцоолъё
кодын гүйцэтгэл. 1xB0 тодорхой хаягийг R130-д ачаалж, түүнээс 5-ыг хасаад R0 руу шилжүүлж, 0x10-ыг нэмнэ. Энэ нь 0xB13B болж байна. Тиймээс, IDA сүүлийн заавар нь хэвийн функцийн өгөөж гэж бодож байгаа боловч үнэн хэрэгтээ энэ нь тооцоолсон хаяг 0xB13B руу явж байна.

ARM процессорууд нь ARM болон Thumb гэсэн хоёр горим, хоёр багц заавартай байдаг гэдгийг энд санах нь зүйтэй. Хаягийн хамгийн бага ач холбогдол бүхий бит нь процессорт ямар зааврын багцыг ашиглаж байгааг хэлж өгдөг. Энэ нь хаяг нь үнэндээ 0xB13A бөгөөд хамгийн бага ач холбогдол бүхий битийн нэг нь Thumb горимыг илтгэнэ.

Энэ номын сангийн функц бүрийн эхэнд ижил төстэй "адаптер" нэмэгдсэн ба
хог код. Бид тэдгээрийг нарийвчлан авч үзэхгүй - зүгээр л санаж байна
бараг бүх функцүүдийн жинхэнэ эхлэл нь арай хол байдаг.

Код нь 0xB13A руу шууд үсрээгүй тул IDA өөрөө код нь энэ байршилд байгааг хүлээн зөвшөөрөөгүй. Үүнтэй ижил шалтгаанаар номын сан дахь ихэнх кодыг код гэж хүлээн зөвшөөрдөггүй бөгөөд энэ нь дүн шинжилгээ хийхэд хүндрэл учруулдаг. Энэ бол код гэдгийг бид IDA-д хэлэхэд ийм зүйл тохиолддог:

UC Browser-ийн эмзэг байдлыг хайж байна

Хүснэгт нь 0xB144-ээс эхэлдэг. Sub_494C-д юу байгаа вэ?

UC Browser-ийн эмзэг байдлыг хайж байна

LR бүртгэлд энэ функцийг дуудахдаа бид өмнө нь дурдсан хүснэгтийн хаягийг (0xB144) авна. R0-д - энэ хүснэгтийн индекс. Өөрөөр хэлбэл утгыг хүснэгтээс аваад LR дээр нэмээд үр дүн нь гарна
очих хаяг. Үүнийг тооцоолохыг хичээцгээе: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Бид хүлээн авсан хаяг руу очоод хэд хэдэн хэрэгтэй зааврыг хараад дахин 0xB140 руу очно уу.

UC Browser-ийн эмзэг байдлыг хайж байна

Одоо хүснэгтээс 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 Browser-ийн эмзэг байдлыг хайж байна

IDA дахин энэ бүсийг код гэж хүлээн зөвшөөрөөгүй. Бид түүнд тусалж, өөр дизайныг харах болно:

UC Browser-ийн эмзэг байдлыг хайж байна

BLX-ийн дараах зааврууд нь нэг их утгагүй мэт санагдаж байна, энэ нь ямар нэг нүүлгэн шилжүүлэлттэй адил юм. sub_4964-ийг харцгаая:

UC Browser-ийн эмзэг байдлыг хайж байна

Үнэн хэрэгтээ энд LR-д байрлах хаяг дээр dword авч, энэ хаяг руу нэмж оруулсны дараа үүссэн хаягийн утгыг авч стек дээр тавьдаг. Мөн LR-д 4-ийг нэмсэн бөгөөд ингэснээр функцээс буцаж ирсний дараа ижил офсет алгасах болно. Үүний дараа POP {R1} команд нь гарсан утгыг стекээс авна. Хэрэв та 0xB4BA + 0xEA = 0xB5A4 хаяг дээр юу байгааг харвал хаягийн хүснэгттэй төстэй зүйлийг харах болно.

UC Browser-ийн эмзэг байдлыг хайж байна

Энэ загварыг нөхөхийн тулд та кодоос хоёр параметрийг авах шаардлагатай: офсет ба үр дүнг оруулахыг хүссэн бүртгэлийн дугаар. Боломжит бүртгэл бүрийн хувьд та кодыг урьдчилан бэлтгэх хэрэгтэй.

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 Browser-ийн эмзэг байдлыг хайж байна

Өмнө дурьдсан бүтцээс гадна код нь дараахь зүйлийг агуулна.

UC Browser-ийн эмзэг байдлыг хайж байна

Өмнөх тохиолдлын нэгэн адил BLX зааврын дараа офсет байна:

UC Browser-ийн эмзэг байдлыг хайж байна

Бид LR-ээс хаяг руу офсет авч, LR дээр нэмээд тийшээ очно. 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 Browser-ийн эмзэг байдлыг хайж байна

Функцэд бүх зүйл засварлагдсаны дараа та IDA-г жинхэнэ эхлэл рүү нь зааж өгч болно. Энэ нь бүх функцийн кодыг нэгтгэх бөгөөд үүнийг HexRays ашиглан задлах боломжтой.

Мөрүүдийг тайлах

Бид номын санд машины кодыг бүдгэрүүлэх асуудлыг шийдэж сурсан libsgmainso-6.4.36.so UC Browser-аас функцийн кодыг хүлээн авсан JNI_Ачаалалтай.

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);

Функцэд дэд_73E24 ангийн нэрийг тайлж байгаа нь тодорхой. Энэ функцийн параметрийн хувьд шифрлэгдсэн өгөгдөлтэй төстэй өгөгдөлд заагч, тодорхой буфер, тоо дамждаг. Функцийг дуудсаны дараа буфер дотор шифрлэгдсэн мөр байх болно, учир нь энэ нь функц руу дамждаг. Анги хайх, энэ нь ангийн нэрийг хоёр дахь параметр болгон авдаг. Тиймээс тоо нь буферийн хэмжээ эсвэл шугамын урт юм. Ангийн нэрийг тайлахыг хичээцгээе, энэ нь биднийг зөв чиглэлд явж байгаа эсэхийг хэлэх ёстой. -д юу тохиолдохыг нарийвчлан авч үзье дэд_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;
}

үйл ажиллагаа дэд_7AF78 нь заасан хэмжээтэй байт массивын контейнерын жишээг үүсгэдэг (бид эдгээр савны талаар дэлгэрэнгүй ярихгүй). Энд хоёр ийм сав бий болсон: нэг нь мөрийг агуулна "DcO/lcK+h?m3c*q@" (энэ нь түлхүүр гэдгийг таахад амархан), нөгөө нь шифрлэгдсэн өгөгдлийг агуулдаг. Дараа нь хоёр объектыг тодорхой бүтцэд байрлуулсан бөгөөд энэ нь функцэд шилждэг дэд_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-р тохиолдлыг харна уу: функцэд дэд_6364C параметрүүдийг өмнөх функц дээр нэмсэн бүтцээс дамжуулдаг, тухайлбал түлхүүр болон шифрлэгдсэн өгөгдөл. Хэрэв та анхааралтай ажиглавал дэд_6364C, та үүн доторх RC4 алгоритмыг таньж болно.

Бидэнд алгоритм, түлхүүр бий. Ангийн нэрийг тайлахыг оролдъё. Юу болсныг энд харуулав. com/taobao/wireless/security/adapter/JNICLibrary. Агуу их! Бид зөв зам дээр байна.

Тушаалын мод

Одоо бид сорилт олох хэрэгтэй Төрөлх иргэдийг бүртгэх, энэ нь биднийг функц рүү чиглүүлэх болно doCommandNative. -аас дуудагдсан функцуудыг харцгаая JNI_Ачаалалтай, мөн бид үүнийг дотроос олдог дэд_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 Browser-ийн эмзэг байдлыг хайж байна

Модыг динамикаар дүүргэсэн JNI_Ачаалалтай.
Гурван тоо нь модны замыг кодлодог. Модны навч бүр нь холбогдох функцийн хаягийг агуулдаг. Түлхүүр нь эцэг эхийн зангилаанд байна. Хэрэв та ашигласан бүх бүтцийг ойлгож байгаа бол кодонд шаардлагатай функцийг мод дээр нэмж оруулах газрыг олох нь тийм ч хэцүү биш юм (бид аль хэдийн нэлээд том нийтлэлийг хавчихгүйн тулд тэдгээрийг тайлбарлаагүй).

Илүү их төөрөгдөл

Бид траффикийн кодыг тайлах функцийн хаягийг хүлээн авлаа: 0x5F1AC. Гэхдээ баярлахад эрт байна: UC Browser-ийн хөгжүүлэгчид бидэнд бас нэгэн сюрприз бэлдсэн.

Java кодонд үүссэн массиваас параметрүүдийг хүлээн авсны дараа бид авна
0x4D070 хаяг дахь функц руу. Энд бас нэг төрлийн кодын төөрөгдөл биднийг хүлээж байна.

Бид R7 ба R4-д хоёр индекс тавьдаг:

UC Browser-ийн эмзэг байдлыг хайж байна

Бид эхний индексийг R11 руу шилжүүлнэ:

UC Browser-ийн эмзэг байдлыг хайж байна

Хүснэгтээс хаяг авахын тулд индексийг ашиглана уу:

UC Browser-ийн эмзэг байдлыг хайж байна

Эхний хаяг руу очсоны дараа R4-д байгаа хоёр дахь индексийг ашиглана. Хүснэгтэнд 230 элемент байна.

Энэ талаар юу хийх вэ? Та IDA-д үүнийг шилжүүлэгч гэж хэлж болно: Edit -> Other -> Specify switch idiom.

UC Browser-ийн эмзэг байдлыг хайж байна

Үүний үр дүнд гарсан код нь аймшигтай юм. Гэхдээ ширэнгэн ойгоор дамжин өнгөрөхдөө та бидэнд аль хэдийн танил болсон функц руу залгаж байгааг анзаарах болно дэд_6115C:

UC Browser-ийн эмзэг байдлыг хайж байна

3-р тохиолдолд RC4 алгоритмыг ашиглан шифрийг тайлсан шилжүүлэгч байсан. Мөн энэ тохиолдолд функцэд дамжуулсан бүтэц нь дамжуулагдсан параметрүүдээс дүүрдэг doCommandNative. Тэнд юу байсныг санацгаая magicInt утгатай 16. Бид харгалзах тохиолдлыг харж, хэд хэдэн шилжилтийн дараа алгоритмыг тодорхойлж болох кодыг олдог.

UC Browser-ийн эмзэг байдлыг хайж байна

Энэ бол AES!

Алгоритм байгаа бөгөөд түүний параметрүүдийг авах л үлдлээ: горим, түлхүүр, магадгүй эхлүүлэх вектор (түүний оршихуй нь AES алгоритмын ажиллах горимоос хамаарна). Тэдгээрийн бүтэц нь функцийг дуудахаас өмнө хаа нэгтээ үүссэн байх ёстой дэд_6115C, гэхдээ кодын энэ хэсэг нь маш сайн ойлгомжгүй байдаг тул кодыг тайлах функцийн бүх параметрүүдийг файлд хаяхын тулд кодыг нөхөх санаа гарч ирдэг.

Нөхөөс

Бүх нөхөөсийн кодыг ассемблер хэлээр гараар бичихгүйн тулд та Android Studio-г ажиллуулж, манай код тайлах функцтэй ижил оролтын параметрүүдийг хүлээн авч файл руу бичдэг функцийг бичиж, хөрвүүлэгчийн өгөх кодыг хуулж буулгаж болно. үүсгэх.

Манай UC Browser багийн найзууд ч гэсэн код нэмэхэд хялбар байх талаар санаа тавьсан. Функц бүрийн эхэнд бид өөр ямар ч кодоор амархан сольж болох хог код байдаг гэдгийг санаарай. Маш тохиромжтой 🙂 Гэсэн хэдий ч зорилтот функцийн эхэнд бүх параметрүүдийг файлд хадгалах код хангалттай зай байхгүй байна. Би үүнийг хэсэг болгон хувааж, хөрш зэргэлдээ функцүүдийн хог хаягдлыг ашиглах шаардлагатай болсон. Нийт дөрвөн хэсэг байсан.

Эхний хэсэг:

UC Browser-ийн эмзэг байдлыг хайж байна

ARM архитектурын хувьд эхний дөрвөн функцын параметрийг R0-R3 регистрээр дамжуулж, үлдсэнийг нь стекээр дамжуулдаг. LR бүртгэл нь буцах хаягийг агуулна. Бид параметрүүдийг нь хаясны дараа функц ажиллахын тулд энэ бүгдийг хадгалах шаардлагатай. Мөн бид энэ процесст ашиглах бүх бүртгэлээ хадгалах шаардлагатай тул PUSH.W {R0-R10,LR}-г хийнэ. R7-д бид стекээр дамжуулан функцэд дамжуулагдсан параметрүүдийн жагсаалтын хаягийг авдаг.

Функцийг ашиглах fopen файлаа нээцгээе /data/local/tmp/aes "ab" горимд
өөрөөр хэлбэл нэмэлт. R0-д бид файлын нэрний хаягийг, R1-д горимыг харуулсан шугамын хаягийг ачаална. Энд хог код дуусч, бид дараагийн функц руу шилжинэ. Үүнийг үргэлжлүүлэн ажиллуулахын тулд бид хогийг тойрч, функцын бодит код руу шилжиж, хогийн оронд нөхөөсийн үргэлжлэлийг нэмдэг.

UC Browser-ийн эмзэг байдлыг хайж байна

Дуудаж байна fopen.

Функцийн эхний гурван параметр aes төрөлтэй INT. Бид регистрүүдийг эхэнд нь стек рүү хадгалсан тул функцийг зүгээр л дамжуулж болно дахин бичих стек дээрх тэдгээрийн хаягууд.

UC Browser-ийн эмзэг байдлыг хайж байна

Дараа нь бид өгөгдлийн хэмжээ, түлхүүр, эхлүүлэх вектор болон шифрлэгдсэн өгөгдлийн өгөгдөлд заагчийг агуулсан гурван бүтэцтэй байна.

UC Browser-ийн эмзэг байдлыг хайж байна

Төгсгөлд нь файлыг хааж, бүртгэлийг сэргээж, удирдлагыг бодит функц руу шилжүүлээрэй aes.

Бид засварласан номын сан бүхий APK цуглуулж, гарын үсэг зурж, төхөөрөмж/эмулятор руу байршуулж, ажиллуулдаг. Манай хогийн цэг бий болж, тэнд маш их өгөгдөл бичигдэж байгааг бид харж байна. Хөтөч нь зөвхөн замын хөдөлгөөнд зориулж шифрлэлтийг ашигладаг бөгөөд бүх шифрлэлт нь тухайн функцээр дамждаг. Гэхдээ зарим шалтгааны улмаас шаардлагатай өгөгдөл байхгүй, шаардлагатай хүсэлт нь замын хөдөлгөөнд харагдахгүй байна. UC Browser шаардлагатай хүсэлтийг гаргахыг хүлээхгүйн тулд өмнө нь хүлээн авсан серверээс шифрлэгдсэн хариултыг авч, програмыг дахин засъя: үндсэн үйл ажиллагааны 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

Бид угсрах, гарын үсэг зурах, суурилуулах, эхлүүлэх. Арга нь null гэж буцаасан тул бид NullPointerException хүлээн авдаг.

Кодын цаашдын дүн шинжилгээ хийх явцад "META-INF/" ба ".RSA" гэсэн сонирхолтой мөрүүдийг тайлах функц олдсон. Аппликешн нь гэрчилгээгээ баталгаажуулж байгаа бололтой. Эсвэл бүр үүнээс түлхүүр үүсгэдэг. Би гэрчилгээнд юу болж байгаатай харьцахыг үнэхээр хүсэхгүй байгаа тул бид үүнийг зөв гэрчилгээтэй болгох болно. Шифрлэгдсэн мөрийг нөхөж, "META-INF/"-ийн оронд "BLABLINF/"-г авч, APK дээр ийм нэртэй хавтас үүсгэж, тэнд хэрэм хөтчийн гэрчилгээг нэмнэ үү.

Бид угсрах, гарын үсэг зурах, суурилуулах, эхлүүлэх. Бинго! Бидэнд түлхүүр байна!

MitM

Бид түлхүүртэй тэнцэх түлхүүр болон эхлүүлэх векторыг хүлээн авсан. CBC горимд серверийн хариултыг тайлахыг оролдъё.

UC Browser-ийн эмзэг байдлыг хайж байна

Бид архивын URL, MD5-тай төстэй зүйл, "extract_unzipsize" болон тоог харж байна. Бид шалгаж байна: архивын MD5 ижил, задалсан номын сангийн хэмжээ ижил байна. Бид энэ номын санг нөхөж, хөтөч рүү өгөхийг оролдож байна. Манай засварласан номын сан ачаалагдсаныг харуулахын тулд бид "PWNED!" Тексттэй SMS үүсгэх санааг эхлүүлэх болно. Бид серверийн хоёр хариултыг солих болно: puds.ucweb.com/upgrade/index.xhtml болон архивыг татаж авах. Эхнийх нь бид MD5-ийг сольсон (зайлсны дараа хэмжээ нь өөрчлөгддөггүй), хоёрдугаарт бид засварласан номын сан бүхий архивыг өгдөг.

Хөтөч нь архивыг хэд хэдэн удаа татаж авахыг оролдсоны дараа алдаа гаргадаг. Ямар нэг юм бололтой
тэр дургүй. Энэхүү бүдэг бадаг форматыг задлан шинжилсний үр дүнд сервер нь архивын хэмжээг бас дамжуулдаг болох нь тогтоогджээ.

UC Browser-ийн эмзэг байдлыг хайж байна

Энэ нь LEB128-д кодлогдсон. Засвар хийсний дараа номын сантай архивын хэмжээ бага зэрэг өөрчлөгдсөн тул хөтөч нь архивыг хазайлтаар татсан гэж үзээд хэд хэдэн оролдлогын дараа алдаа гаргасан.

Бид архивын хэмжээг тохируулдаг ... Тэгээд – ялалт! 🙂 Үр дүн нь видеон дээр байна.

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

Үр дагавар ба хөгжүүлэгчийн хариу үйлдэл

Үүний нэгэн адил хакерууд UC Browser-ийн аюулгүй байдлыг хангахгүй функцийг ашиглан хортой сангуудыг тарааж, ажиллуулж болно. Эдгээр сангууд нь хөтчийн контекст дээр ажиллах тул системийн бүх зөвшөөрлийг хүлээн авах болно. Үүний үр дүнд фишинг цонхыг харуулах, мөн мэдээллийн санд хадгалагдсан нэвтрэлт, нууц үг, күүки зэрэг улбар шар хятад хэрэмний ажлын файлуудад хандах боломжтой болсон.

Бид UC Browser-ийн хөгжүүлэгчидтэй холбогдож, олж мэдсэн асуудлынхаа талаар мэдээлж, эмзэг байдал, түүний аюулыг зааж өгөхийг оролдсон боловч тэд бидэнтэй юу ч хэлэлцээгүй. Үүний зэрэгцээ хөтөч нь аюултай шинж чанараа ил тод харагдсаар байв. Гэхдээ бид эмзэг байдлын нарийн ширийнийг илчилсний дараа өмнөх шигээ үүнийг үл тоомсорлох боломжгүй болсон. Гуравдугаар сарын 27 байсан
HTTPS-ээр серверт хандсан UC Browser 12.10.9.1193-ийн шинэ хувилбар гарсан: puds.ucweb.com/upgrade/index.xhtml.

Нэмж дурдахад, "засах" дараа, энэ нийтлэлийг бичих хүртэл хөтөч дээр PDF файл нээхийг оролдоход "Уучлаарай, ямар нэг зүйл буруу боллоо!" PDF файл нээхийг оролдох үед серверт хүсэлт гаргаагүй боловч хөтөчийг эхлүүлэх үед хүсэлт гаргасан нь Google Play дүрмийг зөрчиж гүйцэтгэх кодыг үргэлжлүүлэн татаж авах боломжтой байгааг харуулж байна.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх