በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

መግቢያ

በመጋቢት መጨረሻ እኛ ዘግቧልበዩሲ ብሮውዘር ውስጥ ያልተረጋገጠ ኮድ የመጫን እና የማስኬድ ድብቅ ችሎታ እንዳገኙ። ዛሬ ይህ ውርድ እንዴት እንደሚከሰት እና ጠላፊዎች ለራሳቸው ዓላማ እንዴት እንደሚጠቀሙበት በዝርዝር እንመለከታለን።

ከተወሰነ ጊዜ በፊት ዩሲ ብሮውዘር ማስታወቂያ ተነግሮ በጣም ኃይለኛ በሆነ መልኩ ተሰራጭቷል፡ ማልዌርን በመጠቀም በተጠቃሚዎች መሳሪያዎች ላይ ተጭኗል፣ ከተለያዩ ድረ-ገጾች በቪዲዮ ፋይሎች ተሰራጭቷል (ማለትም፣ ተጠቃሚዎች እያወረዱ ነበር ብለው ያስቡ ነበር ለምሳሌ የወሲብ ቪዲዮ፣ ግን በምትኩ ከዚህ አሳሽ ጋር አንድ ኤፒኬ ተቀብሏል)፣ አሳሹ ጊዜው ያለፈበት፣ ለጥቃት የተጋለጠ እና መሰል ነገሮችን የያዘ አስፈሪ ባነሮችን ተጠቅሟል። በ VK ላይ ባለው ኦፊሴላዊ የዩሲ አሳሽ ቡድን ውስጥ አለ። ገጽታተጠቃሚዎች ስለ ኢፍትሃዊ ማስታወቂያ ቅሬታ ማቅረብ የሚችሉባቸው ብዙ ምሳሌዎች አሉ። በ 2016 እንኳን ነበር የቪዲዮ ማስታወቂያ በሩሲያኛ (አዎ፣ ማስታወቂያ የሚያግድ አሳሽ)።

ይህ ጽሑፍ በሚጻፍበት ጊዜ፣ ዩሲ አሳሽ በጎግል ፕሌይ ላይ ከ500 በላይ ጭነቶች አሉት። ይህ አስደናቂ ነው - ጎግል ክሮም ብቻ ብዙ አለው። ከግምገማዎቹ መካከል ስለማስታወቂያ እና በጎግል ፕሌይ ላይ ወደ አንዳንድ አፕሊኬሽኖች ማዘዋወር ብዙ ቅሬታዎችን ማየት ይችላሉ። ለጥናታችን ምክንያቱ ይህ ነበር፡ ዩሲ ብሮውዘር መጥፎ ነገር እየሰራ መሆኑን ለማየት ወሰንን። እና እሱ እንደሚያደርግ ተገለጠ!

በመተግበሪያው ኮድ ውስጥ ፣ የሚተገበር ኮድ የማውረድ እና የማሄድ ችሎታ ተገኝቷል ፣ ማመልከቻዎችን ለማተም ደንቦችን የሚጻረር ነው በጎግል ፕሌይ ላይ። ዩሲ ብሮውዘር executable code ከማውረድ በተጨማሪ ደህንነቱ ባልተጠበቀ መንገድ የሚያደርገው ሲሆን ይህም ሚትኤም ጥቃት ለመሰንዘር ይጠቅማል። እንዲህ ዓይነት ጥቃት መፈጸም እንደምንችል እንይ።

ከዚህ በታች የተፃፈው ሁሉም ነገር በጥናቱ ወቅት በጎግል ፕሌይ ላይ ለነበረው የUC ብሮውዘር ስሪት ጠቃሚ ነው።

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

የጥቃት ቬክተር

በዩሲ ብሮውዘር ማኒፌክት ውስጥ በራስ ገላጭ ስም ያለው አገልግሎት ማግኘት ይችላሉ። com.uc.deployment.UpgradeDeploy Service.

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

ይህ አገልግሎት ሲጀምር አሳሹ የPOST ጥያቄ ያቀርባል puds.ucweb.com/upgrade/index.xhtml, ይህም ከመነሻው ከተወሰነ ጊዜ በኋላ በትራፊክ ውስጥ ሊታይ ይችላል. በምላሹ, አንዳንድ ዝመናዎችን ወይም አዲስ ሞጁሎችን ለማውረድ ትእዛዝ ሊደርሰው ይችላል. በመተንተን ወቅት አገልጋዩ እንደዚህ አይነት ትዕዛዞችን አልሰጠም, ነገር ግን በአሳሹ ውስጥ ፒዲኤፍ ለመክፈት ስንሞክር, ከላይ ለተጠቀሰው አድራሻ ሁለተኛ ጥያቄ እንደሚያቀርብ አስተውለናል, ከዚያ በኋላ ቤተ-መጽሐፍትን ያወርዳል. ጥቃቱን ለመፈጸም ይህንን የዩሲ አሳሽ ባህሪ ለመጠቀም ወስነናል፡ ፒዲኤፍን የመክፈት ችሎታ በኤፒኬ ውስጥ የሌለ እና አስፈላጊ ከሆነ ከበይነመረቡ የሚያወርድ ነው። በንድፈ ሀሳብ, ዩሲ ብሮውዘር ያለተጠቃሚ መስተጋብር አንድ ነገር ለማውረድ ሊገደድ እንደሚችል ልብ ሊባል የሚገባው ነው - አሳሹ ከተከፈተ በኋላ ለሚደረገው ጥያቄ ጥሩ ቅርጽ ያለው ምላሽ ከሰጡ. ነገር ግን ይህንን ለማድረግ ከአገልጋዩ ጋር ያለውን የግንኙነት ፕሮቶኮል በዝርዝር ማጥናት አለብን, ስለዚህ የተጠለፈውን ምላሽ ማስተካከል እና ቤተ-መጽሐፍቱን በፒዲኤፍ ለመስራት ቀላል እንዲሆን ወስነናል.

ስለዚህ አንድ ተጠቃሚ ፒዲኤፍ በቀጥታ በአሳሹ ውስጥ መክፈት ሲፈልግ የሚከተሉት ጥያቄዎች በትራፊክ ውስጥ ሊታዩ ይችላሉ።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በመጀመሪያ የPOST ጥያቄ አለ። puds.ucweb.com/upgrade/index.xhtmlከዚያ በኋላ
ፒዲኤፍ እና የቢሮ ቅርጸቶችን ለማየት ቤተ-መጽሐፍት ያለው መዝገብ ይወርዳል። የመጀመሪያው ጥያቄ ስለ ስርዓቱ መረጃ እንደሚያስተላልፍ መገመት ምክንያታዊ ነው (ቢያንስ አርክቴክቸር አስፈላጊውን ቤተ-መጽሐፍት ለማቅረብ) እና ለእሱ ምላሽ አሳሹ ማውረድ ስለሚያስፈልገው ቤተ-መጽሐፍት የተወሰነ መረጃ ይቀበላል-አድራሻውን እና ምናልባትም ፣ ሌላ ነገር። ችግሩ ይህ ጥያቄ የተመሰጠረ መሆኑ ነው።

ቁርጥራጭ ይጠይቁ

ቁርጥራጭ መልስ

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ቤተ መፃህፍቱ ራሱ በዚፕ ውስጥ የታሸገ ነው እና አልተመሰጠረም።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

የትራፊክ ዲክሪፕት ኮድ ይፈልጉ

የአገልጋዩን ምላሽ ለመፍታት እንሞክር። የክፍል ኮድን እንይ com.uc.deployment.UpgradeDeploy Serviceከዘዴ በStartCommand ላይ መሄድ com.uc.ማሰማራት.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") በመመዘን የአገልጋዩን ምላሽ የሚፈታ ዘዴ እዚህ መጠራት አለበት።
ወደ ዘዴው እንሂድ 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 ጋር እኩል የሆነ፣ ከሶስት ሊሆኑ ከሚችሉ አማራጮች አንዱን ያመለክታል።

ኮዱን መተንተን እንቀጥላለን. ከአንድ ሁለት ዝላይ በኋላ እራሳችንን ገላጭ ስም ባለው ዘዴ ውስጥ እናገኛለን ዲክሪፕት ባይtesByKey.

እዚህ ሁለት ተጨማሪ ባይቶች ከምላሻችን ተለያይተዋል, እና ከእነሱ አንድ ሕብረቁምፊ ተገኝቷል. በዚህ መንገድ መልእክቱን ዲክሪፕት ለማድረግ ቁልፉ እንደተመረጠ ግልጽ ነው.

    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, እሱም በእውነቱ .ሶ ሳይሆን .ጀር. እኛ የምንፈልገው ዘዴ እንደሚከተለው ተተግብሯል.

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 እንደ ዘዴው ዲክሪፕት ማድረግ ማለት ነው doFinal የስርዓት ክፍል javax.crypto.Cipher. እና ይህ ሁሉ ቁጥር 10601 ወዳለው የተወሰነ ራውተር ተላልፏል - ይህ በግልጽ የትእዛዝ ቁጥር ነው።

ከሚቀጥለው የሽግግር ሰንሰለት በኋላ በይነገጽን የሚተገበር ክፍል እናገኛለን IRouterComponent እና ዘዴ ትእዛዝ:

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

እንዲሁም ክፍል JNICLIbrary, የአገሬው ተወላጅ ዘዴ የታወጀበት doCommandNative:

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

ይህ ማለት በአፍ መፍቻ ኮድ ውስጥ ዘዴ መፈለግ አለብን ማለት ነው doCommandNative. እና መዝናኛው የሚጀምረው እዚህ ነው።

የማሽን ኮድ መደበቅ

በፋይል ውስጥ libsgmain.ሶ (በእውነቱ .jar ነው እና አንዳንድ ከምስጠራ ጋር የተገናኙ በይነገጾች መተግበርን ያገኘንበት) አንድ ቤተኛ ቤተ-መጽሐፍት አለ፡- libsgmainso-6.4.36.ሶ. በ IDA ውስጥ እንከፍተዋለን እና ስህተቶች ያሏቸው ብዙ የንግግር ሳጥኖችን እናገኛለን። ችግሩ የክፍል ራስጌ ሠንጠረዥ ልክ ያልሆነ ነው። ይህ የሚደረገው ትንታኔውን ለማወሳሰብ ሆን ተብሎ ነው።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ግን አያስፈልግም: የኤልኤፍ ፋይል በትክክል ለመጫን እና ለመተንተን, የፕሮግራም ራስጌ ሰንጠረዥ በቂ ነው. ስለዚህ, በቀላሉ የሴክሽን ሠንጠረዥን እንሰርዛለን, በርዕሱ ውስጥ ያሉትን ተዛማጅ መስኮች ዜሮ እናደርጋለን.

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ፋይሉን በ IDA ውስጥ እንደገና ይክፈቱ።

ለጃቫ ቨርቹዋል ማሽን በትክክል በቤተ-መጽሐፍት ውስጥ በጃቫ ኮድ ቤተኛ ተብሎ የተገለጸው ዘዴ የት እንደሚገኝ ለመንገር ሁለት መንገዶች አሉ። የመጀመሪያው የዝርያ ስም መስጠት ነው የጃቫ_ጥቅል_ስም_የክፍል ስም_ዘዴ ስም.

ሁለተኛው ቤተ መፃህፍቱን ሲጭኑ መመዝገብ ነው (በተግባር ውስጥ JNI_ተጭኗል)
የተግባር ጥሪን በመጠቀም የተመዘገቡ ተወላጆች.

በእኛ ሁኔታ, የመጀመሪያውን ዘዴ ከተጠቀምን, ስሙ እንደዚህ መሆን አለበት. ጃቫ_ኮም_ታኦባኦ_ገመድ_አልባ_ደህንነት_አስማሚ_ጄኒሲሊብራሪ_የትእዛዝ ተወላጅ.

ወደ ውጭ ከተላኩ ተግባራት መካከል እንደዚህ ያለ ተግባር የለም, ይህ ማለት ጥሪ መፈለግ ያስፈልግዎታል ማለት ነው የተመዘገቡ ተወላጆች.
ወደ ተግባሩ እንሂድ JNI_ተጭኗል እና ይህንን ምስል እናያለን-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

እዚህ ምን እየሆነ ነው? በመጀመሪያ እይታ, የተግባሩ መጀመሪያ እና መጨረሻ ለ ARM ስነ-ህንፃዎች የተለመዱ ናቸው. በመደርደሪያው ላይ ያለው የመጀመሪያው መመሪያ ተግባሩ በሚሠራበት ጊዜ (በዚህ ጉዳይ ላይ R0, R1 እና R2) የሚጠቀመውን የመመዝገቢያውን ይዘቶች ያከማቻል, እንዲሁም የ LR መመዝገቢያ ይዘቶች, ከተግባሩ የመመለሻ አድራሻን ይይዛል. . የመጨረሻው መመሪያ የተቀመጡ መዝገቦችን ወደነበረበት ይመልሳል, እና የመመለሻ አድራሻው ወዲያውኑ በፒሲ መመዝገቢያ ውስጥ ይቀመጣል - ስለዚህ ከተግባሩ ይመለሳል. ነገር ግን በቅርበት ከተመለከቱ፣ ቀጣሪው መመሪያው ቁልል ላይ የተቀመጠውን የመመለሻ አድራሻ እንደሚቀይር ያስተውላሉ። ከዚያ በኋላ ምን እንደሚሆን እናሰላለን
ኮድ አፈፃፀም ። የተወሰነ አድራሻ 1xB0 ወደ R130 ተጭኗል, 5 ከእሱ ይቀንሳል, ከዚያም ወደ R0 እና 0x10 ይጨመርበታል. 0xB13B ይወጣል. ስለዚህ, IDA የመጨረሻው መመሪያ መደበኛ ተግባር መመለሻ ነው ብሎ ያስባል, ነገር ግን በእውነቱ ወደ ስሌት አድራሻ 0xB13B ይሄዳል.

እዚህ ላይ የ ARM ፕሮሰሰሮች ሁለት ሁነታዎች እና ሁለት መመሪያዎች እንዳላቸው ማስታወስ ጠቃሚ ነው: ARM እና Thumb. የአድራሻው ትንሹ ጉልህ ቢት የትኛው መመሪያ ስብስብ ጥቅም ላይ እንደሚውል ለአቀነባባሪው ይነግረዋል። ያም ማለት አድራሻው በትክክል 0xB13A ነው, እና በትንሹ ጉልህ በሆነ መልኩ አንዱ የ Thumb ሁነታን ያመለክታል.

በዚህ ቤተ-መጽሐፍት ውስጥ እና በእያንዳንዱ ተግባር መጀመሪያ ላይ ተመሳሳይ “አስማሚ” ተጨምሯል።
የቆሻሻ ኮድ. በእነሱ ላይ በዝርዝር አንቀመጥም - እናስታውሳለን
የሁሉም ተግባራት እውነተኛ ጅምር ትንሽ ሩቅ እንደሆነ።

ኮዱ ወደ 0xB13A በግልጽ ስለማይዘል፣ IDA ራሱ ኮዱ እዚህ ቦታ ላይ እንደሚገኝ አላወቀም። በተመሳሳዩ ምክንያት, በቤተ-መጽሐፍት ውስጥ ያሉትን አብዛኛዎቹን ኮድ እንደ ኮድ አይገነዘብም, ይህም ትንታኔን በመጠኑ አስቸጋሪ ያደርገዋል. ይህ ኮድ መሆኑን ለአይዲኤ እንነግራቸዋለን፣ እና የሆነውም ይኸው ነው፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ሠንጠረዡ በግልጽ በ0xB144 ይጀምራል። በንዑስ_494C ውስጥ ምን አለ?

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ይህንን ተግባር በ LR መመዝገቢያ ውስጥ ሲደውሉ, ቀደም ሲል የተጠቀሰው ሰንጠረዥ አድራሻ (0xB144) እናገኛለን. በ R0 - በዚህ ሰንጠረዥ ውስጥ መረጃ ጠቋሚ. ያም ማለት እሴቱ ከጠረጴዛው ላይ ተወስዷል, ወደ LR ተጨምሯል እና ውጤቱም ነው
የሚሄዱበት አድራሻ. እሱን ለማስላት እንሞክር፡ 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. ወደ ተቀበለው አድራሻ እንሄዳለን እና በትክክል ሁለት ጠቃሚ መመሪያዎችን እናያለን እና እንደገና ወደ 0xB140 እንሄዳለን፡

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

አሁን ከሠንጠረዡ በመረጃ ጠቋሚ 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 የሚደረገውን ሽግግር ይመልከቱ፡

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

IDA ይህን አካባቢ እንደ ኮድ በድጋሚ አላወቀውም። እኛ እናግዛታለን እና እዚያ ሌላ ንድፍ እናያለን፡

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ከBLX በኋላ ያሉት መመሪያዎች ብዙም ትርጉም ያላቸው አይመስሉም፣ እሱ እንደ አንድ ዓይነት መፈናቀል ነው። ንዑስ_4964ን እንመልከት፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

እና በእውነቱ ፣ እዚህ አንድ dword በ LR ውስጥ ባለው አድራሻ ተወስዷል ፣ ወደዚህ አድራሻ ተጨምሯል ፣ ከዚያ በኋላ በውጤቱ አድራሻ ላይ ያለው እሴት ተወስዶ ቁልል ላይ ይደረጋል። እንዲሁም፣ 4 ወደ LR ተጨምሯል። ከዚያ በኋላ የPOP {R1} ትዕዛዝ የተገኘውን ዋጋ ከቁልል ይወስዳል። በአድራሻ 0xB4BA + 0xEA = 0xB5A4 የሚገኘውን ከተመለከቱ ከአድራሻ ሠንጠረዥ ጋር የሚመሳሰል ነገር ያያሉ፡

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ይህንን ንድፍ ለመለጠፍ ከኮዱ ሁለት መመዘኛዎችን ማግኘት ያስፈልግዎታል-ማካካሻ እና ውጤቱን ለማስቀመጥ የሚፈልጉት የመመዝገቢያ ቁጥር. ለእያንዳንዱ መመዝገቢያ የሚሆን ኮድ አስቀድመው ማዘጋጀት አለብዎት.

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 - ለመተካት በምንፈልገው መዋቅር መጀመሪያ ላይ ጠቋሚውን እናስቀምጠዋለን እና ስክሪፕቱን እናስኬዳለን።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ቀደም ሲል ከተጠቀሱት አወቃቀሮች በተጨማሪ, ኮዱ የሚከተሉትን ያካትታል:

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

እንደ ቀድሞው ሁኔታ፣ ከBLX መመሪያ በኋላ ማካካሻ አለ፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ማካካሻውን ከ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"

የስክሪፕት አፈጻጸም ውጤት፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

አንዴ ሁሉም ነገር በተግባሩ ውስጥ ከተጣበቀ, IDA ወደ እውነተኛው መጀመሪያው መጠቆም ይችላሉ. ሁሉንም የተግባር ኮድ በአንድ ላይ ይሰበስባል እና ሄክስሬይስን በመጠቀም ሊፈርስ ይችላል።

ሕብረቁምፊዎችን መፍታት

በቤተ-መጽሐፍት ውስጥ የማሽን ኮድ መደበቅን ተምረናል። libsgmainso-6.4.36.ሶ ከዩሲ አሳሽ እና የተግባር ኮድ ተቀብሏል 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/ገመድ አልባ/ደህንነት/አስማሚ/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 እንጥቀስላቸው) የትእዛዝ ዛፍ ይገነባል.

እንደዚህ ያለ ነገር፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ዛፉ በተለዋዋጭነት ተሞልቷል JNI_ተጭኗል.
ሶስት ቁጥሮች በዛፉ ውስጥ ያለውን መንገድ ያመለክታሉ. እያንዳንዱ የዛፉ ቅጠል ተጓዳኝ ተግባሩን የተቀዳ አድራሻ ይይዛል. ቁልፉ በወላጅ መስቀለኛ መንገድ ውስጥ ነው. ጥቅም ላይ የዋሉትን ሁሉንም መዋቅሮች ከተረዱ የሚያስፈልገንን ተግባር ወደ ዛፉ ላይ የሚጨምርበትን ኮድ ውስጥ ቦታ ማግኘት አስቸጋሪ አይደለም (ቀደም ሲል ትልቅ ጽሑፍን ላለማበሳጨት አንገልጻቸውም).

ተጨማሪ መደበቅ

ትራፊክን ዲክሪፕት ማድረግ ያለበት የተግባር አድራሻ ተቀብለናል፡ 0x5F1AC። ግን ለመደሰት በጣም ገና ነው የዩሲ አሳሽ ገንቢዎች ሌላ አስገራሚ ነገር አዘጋጅተውልናል።

በጃቫ ኮድ ውስጥ ከተሰራው ድርድር መለኪያዎችን ከተቀበልን በኋላ እናገኛለን
ወደ ተግባር በአድራሻ 0x4D070. እና እዚህ ሌላ አይነት ኮድ መደበቅ ይጠብቀናል.

በ R7 እና R4 ውስጥ ሁለት ኢንዴክሶችን እናስቀምጣለን-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

የመጀመሪያውን መረጃ ጠቋሚ ወደ R11 እንቀይራለን፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ከጠረጴዛ ላይ አድራሻ ለማግኘት፣ ኢንዴክስ ይጠቀሙ፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ወደ መጀመሪያው አድራሻ ከሄዱ በኋላ, ሁለተኛው ኢንዴክስ ጥቅም ላይ ይውላል, እሱም በ R4 ውስጥ. በሰንጠረዡ ውስጥ 230 ንጥረ ነገሮች አሉ.

ስለ እሱ ምን ማድረግ አለበት? ይህ ማብሪያ / ማጥፊያ መሆኑን ለ IDA መንገር ይችላሉ፡ አርትዕ -> ሌላ -> የመቀየሪያ ፈሊጥ ይግለጹ።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

የተገኘው ኮድ በጣም አስፈሪ ነው. ነገር ግን፣ በጫካው ውስጥ መንገዳችሁን፣ ለእኛ ቀድሞውንም ለምናውቀው ተግባር ጥሪን ማስተዋል ይችላሉ። ንዑስ_6115C:

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በሁኔታ 3 የ RC4 ስልተ ቀመር በመጠቀም ዲክሪፕት የተደረገበት ማብሪያ / ማጥፊያ ነበር። እናም በዚህ ሁኔታ, ወደ ተግባሩ የተላለፈው መዋቅር ከተተላለፉት መለኪያዎች ተሞልቷል doCommandNative. እዚያ የነበረውን ነገር እናስታውስ magicInt ከዋጋው ጋር 16. ተጓዳኝ ጉዳይን እንመለከታለን - እና ከበርካታ ሽግግሮች በኋላ አልጎሪዝም ሊታወቅ የሚችልበትን ኮድ እናገኛለን.

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

ይህ AES ነው!

አልጎሪዝም አለ ፣ የቀረው ሁሉ የእሱን መመዘኛዎች ማግኘት ነው-ሁነታ ፣ ቁልፍ እና ምናልባትም ፣ የመነሻ ቬክተር (የእሱ መገኘት በ AES ስልተቀመር አሠራር ላይ የተመሠረተ ነው)። ከነሱ ጋር ያለው መዋቅር ከተግባሩ ጥሪ በፊት የሆነ ቦታ መፈጠር አለበት ንዑስ_6115C, ነገር ግን ይህ የኮዱ ክፍል በተለይ በደንብ የተደበቀ ነው, ስለዚህ ሁሉም የዲክሪፕት ተግባር መለኪያዎች ወደ ፋይል ውስጥ እንዲጣሉ ኮዱን ለመጠቅለል ሀሳቡ ይነሳል.

ጠጋኝ

ሁሉንም የ patch ኮድ በመገጣጠሚያ ቋንቋ በእጅ ላለመፃፍ አንድሮይድ ስቱዲዮን ማስጀመር ፣ እዚያም እንደ እኛ ዲክሪፕት የማድረግ ተግባር ተመሳሳይ የግቤት መለኪያዎችን የሚቀበል ተግባር ይፃፉ እና ወደ ፋይል ይፃፉ ፣ ከዚያ ኮፒውተሩ የሚፈልገውን ኮድ ቀድተው ይለጥፉ ። ማመንጨት.

የዩሲ ብሮውዘር ቡድን ጓደኞቻችን ኮድ የመጨመርን ምቾት ይንከባከቡ ነበር። በእያንዳንዱ ተግባር መጀመሪያ ላይ በቀላሉ በሌላ በማንኛውም መተካት የሚችል የቆሻሻ ኮድ እንዳለን እናስታውስ። በጣም ምቹ 🙂 ይሁን እንጂ በዒላማው ተግባር መጀመሪያ ላይ ሁሉንም መለኪያዎች ወደ ፋይል የሚያስቀምጥ ኮድ በቂ ቦታ የለም. ወደ ክፍሎች መከፋፈል እና ከአጎራባች ተግባራት የቆሻሻ መጣያዎችን መጠቀም ነበረብኝ. በአጠቃላይ አራት ክፍሎች ነበሩ.

የመጀመሪያው ክፍል:

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በ ARM አርክቴክቸር ውስጥ የመጀመሪያዎቹ አራት የተግባር መለኪያዎች በመመዝገቢያ R0-R3 በኩል ያልፋሉ, የተቀሩት, ካለ, በቆለሉ ውስጥ ይለፋሉ. የLR መዝገብ የመመለሻ አድራሻውን ይይዛል። ግቤቶቹን ከጣልን በኋላ ተግባሩ እንዲሠራ ይህ ሁሉ መቀመጥ አለበት። እንዲሁም በሂደቱ ውስጥ የምንጠቀማቸውን ሁሉንም መዝገቦች ማስቀመጥ አለብን, ስለዚህ PUSH.W {R0-R10,LR}ን እናደርጋለን. በ R7 ውስጥ በክምችት በኩል ወደ ተግባሩ የሚተላለፉትን የመለኪያዎች ዝርዝር አድራሻ እናገኛለን.

ተግባሩን በመጠቀም ፎፔን ፋይሉን እንከፍተው /data/local/tmp/aes በ "ab" ሁነታ
ማለትም ለመደመር። በ R0 ውስጥ የፋይል ስም አድራሻን እንጭነዋለን, በ R1 ውስጥ - ሁነታውን የሚያመለክት የመስመር አድራሻ. እና እዚህ የቆሻሻ ኮድ ያበቃል, ስለዚህ ወደ ቀጣዩ ተግባር እንቀጥላለን. መስራቱን እንዲቀጥል መጀመሪያ ላይ ወደ ተግባሩ እውነተኛ ኮድ ሽግግርን እናስቀምጣለን, ቆሻሻውን በማለፍ, እና ከቆሻሻው ይልቅ የፕላስተር ቀጣይነት እንጨምራለን.

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በመደወል ላይ ፎፔን.

የተግባሩ የመጀመሪያዎቹ ሶስት መለኪያዎች aes ዓይነት አላቸው int. መጀመሪያ ላይ መዝገቦቹን ወደ ቁልል ስላስቀመጥን, ተግባሩን በቀላሉ ማለፍ እንችላለን ጻፍ ቁልል ላይ ያላቸውን አድራሻዎች.

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በመቀጠልም የመረጃውን መጠን እና ለቁልፍ፣ ማስጀመሪያ ቬክተር እና ኢንክሪፕትድ ዳታ መረጃ ጠቋሚ የያዙ ሶስት አወቃቀሮች አሉን።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በመጨረሻው ላይ ፋይሉን ይዝጉ, መዝገቦቹን ወደነበሩበት ይመልሱ እና መቆጣጠሪያውን ወደ እውነተኛው ተግባር ያስተላልፉ aes.

አንድ ኤፒኬ ከተጣበቀ ቤተ-መጽሐፍት ጋር እንሰበስባለን፣ እንፈርመዋለን፣ ወደ መሳሪያው/ emulator እንሰቅላለን እና እናስጀመርዋለን። የእኛ የቆሻሻ መጣያ እየተፈጠረ መሆኑን እናያለን, እና ብዙ መረጃዎች እዚያ እየተጻፉ ነው. አሳሹ ምስጠራን የሚጠቀመው ለትራፊክ ብቻ አይደለም፣ እና ሁሉም ምስጠራ በጥያቄ ውስጥ ባለው ተግባር ውስጥ ያልፋል። ግን በሆነ ምክንያት አስፈላጊው መረጃ እዚያ የለም, እና አስፈላጊው ጥያቄ በትራፊክ ውስጥ አይታይም. አስፈላጊውን ጥያቄ ለማቅረብ ዩሲ ብሮውዘር እስኪያስተካክል እንዳንጠብቅ ፣ከዚህ ቀደም ከተቀበለው አገልጋይ የተመሰጠረውን ምላሽ እንውሰድ እና አፕሊኬሽኑን እንደገና እንለጥፈው፡ ዋናውን እንቅስቃሴ ፍጠር ላይ ዲክሪፕት እንጨምር።

    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 አግኝተናል።

በኮዱ ላይ ተጨማሪ ትንታኔ በሚሰጥበት ጊዜ አስደሳች መስመሮችን "META-INF/" እና ".RSA" የሚፈታ ተግባር ተገኘ። አፕሊኬሽኑ የእውቅና ማረጋገጫውን የሚያረጋግጥ ይመስላል። ወይም ደግሞ ከእሱ ቁልፎችን ያመነጫል. በእውቅና ማረጋገጫው ላይ ምን እየተከሰተ እንዳለ በትክክል መቋቋም አልፈልግም, ስለዚህ ትክክለኛውን የምስክር ወረቀት ብቻ እናንሸራትታለን. ኢንክሪፕት የተደረገውን መስመር በ"META-INF/" ፈንታ "BLABLINF/" እንድናገኝ፣ በኤፒኬ ውስጥ ያንን ስም የያዘ አቃፊ እንፍጠር እና የስኩዊርል አሳሽ ሰርተፍኬትን እዚያ እንጨምር።

እንሰበስባለን ፣ እንፈርማለን ፣ እንጭናለን ፣ እንጀምራለን ። ቢንጎ! ቁልፉ አለን!

ሚትኤም

ከቁልፍ ጋር እኩል የሆነ ቁልፍ እና የመነሻ ቬክተር ተቀብለናል። በሲቢሲ ሁነታ የአገልጋዩን ምላሽ ዲክሪፕት ለማድረግ እንሞክር።

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

የማህደር ዩአርኤልን፣ ከMD5 ጋር የሚመሳሰል ነገር፣ “ማውጣት_ማስወጣት” እና ቁጥርን እናያለን። እኛ እንፈትሻለን-የማህደሩ ኤምዲ5 ተመሳሳይ ነው ፣ ያልታሸገው ቤተ-መጽሐፍት መጠኑ ተመሳሳይ ነው። ይህንን ቤተ-መጽሐፍት ለጥፈን ለአሳሹ ለመስጠት እየሞከርን ነው። የታሸገው ቤተ-መጽሐፍታችን መጫኑን ለማሳየት፣ “PWNED!” የሚል ጽሁፍ ያለው ኤስኤምኤስ የመፍጠር ሐሳብ እንጀምራለን። ከአገልጋዩ ሁለት ምላሾችን እንተካለን፡- puds.ucweb.com/upgrade/index.xhtml እና ማህደሩን ለማውረድ. በመጀመሪያው ላይ MD5 ን እንተካለን (ከከፈቱ በኋላ መጠኑ አይቀየርም) ፣ በሁለተኛው ውስጥ ማህደሩን በተጣበቀ ቤተ-መጽሐፍት እንሰጠዋለን።

አሳሹ ማህደሩን ብዙ ጊዜ ለማውረድ ይሞክራል, ከዚያ በኋላ ስህተት ይሰጣል. የሆነ ነገር ይመስላል
እሱ አይወድም. ይህንን አጨማጭ ቅርጸት በመተንተን ምክንያት አገልጋዩ የማህደሩን መጠን እንደሚያስተላልፍ ታወቀ፡-

በዩሲ አሳሽ ውስጥ ተጋላጭነቶችን መፈለግ

በLEB128 ውስጥ ተቀምጧል። ከጥበቃው በኋላ ፣የማህደሩ መጠን ከቤተ-መጽሐፍት ጋር ትንሽ ተቀየረ ፣ስለዚህ አሳሹ ማህደሩ በተጭበረበረ መልኩ እንደወረደ አሰበ እና ከብዙ ሙከራዎች በኋላ ስህተት ጣለ።

የማህደሩን መጠን እናስተካክላለን ... እና - ድል! 🙂 ውጤቱ በቪዲዮው ውስጥ ነው.

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

መዘዞች እና የገንቢ ምላሽ

በተመሳሳይ ሁኔታ ሰርጎ ገቦች ተንኮል አዘል ቤተ-መጻሕፍትን ለማሰራጨት እና ለማስኬድ ደህንነቱ ያልተጠበቀውን የUC Browser ባህሪን ሊጠቀሙ ይችላሉ። እነዚህ ቤተ-ፍርግሞች በአሳሹ አውድ ውስጥ ይሰራሉ, ስለዚህ ሁሉንም የስርዓት ፍቃዶቹን ይቀበላሉ. በውጤቱም, የማስገር መስኮቶችን የማሳየት ችሎታ, እንዲሁም የብርቱካን ቻይንኛ ስኩዊር የስራ ፋይሎችን ማግኘት, መግቢያዎች, የይለፍ ቃሎች እና በመረጃ ቋቱ ውስጥ የተከማቹ ኩኪዎችን ጨምሮ.

የዩሲ ብሮውዘርን አዘጋጆች አነጋግረን ያገኘነውን ችግር አሳውቀን፣ ተጋላጭነቱን እና አደጋውን ለመጠቆም ሞከርን ነገርግን ምንም አልተወያየንም። ይህ በእንዲህ እንዳለ፣ አሳሹ አደገኛ ባህሪውን በግልፅ ማየት ቀጠለ። ነገር ግን አንድ ጊዜ የተጋላጭነቱን ዝርዝር ከገለፅን በኋላ እንደበፊቱ ችላ ማለት አይቻልም። ማርች 27 ነበር።
አዲስ የዩሲ አሳሽ 12.10.9.1193 ተለቀቀ፣ እሱም አገልጋዩን በ HTTPS በኩል አግኝቷል፡ puds.ucweb.com/upgrade/index.xhtml.

በተጨማሪም ከ“ማስተካከያ” በኋላ እና ይህን ጽሑፍ እስከተፃፈበት ጊዜ ድረስ ፒዲኤፍ በአሳሹ ውስጥ ለመክፈት መሞከር “ውይ፣ የሆነ ችግር ተፈጥሯል!” የሚል የስህተት መልእክት አስከትሏል። ፒዲኤፍ ለመክፈት በሚሞከርበት ጊዜ ለአገልጋዩ የቀረበ ጥያቄ አልነበረም፣ ነገር ግን አሳሹ ሲጀመር ጥያቄ ቀርቦ ነበር፣ ይህ ደግሞ የጎግል ፕሌይ ህጎችን በመጣስ የሚተገበር ኮድ ማውረድ እንደሚቀጥል ፍንጭ ይሰጣል።

ምንጭ: hab.com

አስተያየት ያክሉ