1. Introdução
No final de março,
informamos que descobrimos uma capacidade oculta de baixar e executar código não verificado no navegador UC. Hoje, analisaremos em detalhes como esse download ocorre e como os hackers podem usá-lo para seus próprios fins.
Há algum tempo, o UC Browser foi anunciado e distribuído de forma muito agressiva: foi instalado nos dispositivos dos usuários usando malware, distribuído em vários sites sob o disfarce de arquivos de vídeo (ou seja, os usuários pensaram ter baixado, por exemplo, um clipe pornô, mas receberam um APK com este navegador), usou banners assustadores com mensagens dizendo que o navegador está desatualizado, vulnerável e coisas assim. O grupo oficial do navegador UC na VK tem um
tópico no qual os usuários podem reclamar de publicidade desleal; existem muitos exemplos. Em 2016, houve até
publicidade em
vídeo em russo (sim, anúncios de um navegador que bloqueia anúncios).
No momento da redação deste artigo, o UC Browser possui mais de 500 milhões de instalações no Google Play. Isso é impressionante - apenas o Google Chrome tem mais. Entre as análises, você pode ver muitas reclamações sobre publicidade e redirecionamentos para alguns aplicativos no Google Play. Esta foi a razão do estudo: decidimos ver se o UC Browser está fazendo algo ruim. E acabou que ele estava fazendo isso!
O código do aplicativo revelou a capacidade de baixar e executar o código executável, o
que contradiz as regras para publicação de aplicativos no Google Play. Além do fato de o UC Browser fazer download de código executável, torna-o inseguro, que pode ser usado para conduzir um ataque do MitM. Vamos ver se conseguimos realizar tal ataque.
Tudo o que está escrito abaixo é relevante para a versão do UC Browser que estava presente no Google Play na época do estudo:
package: com.UCMobile.intl versionName: 12.10.8.1172 versionCode: 10598 sha1 APK-: f5edb2243413c777172f6362876041eb0c3a928c
Vetor de ataque
No manifesto do navegador UC, você pode encontrar um serviço chamado
com.uc.deployment.UpgradeDeployService .
<service android:exported="false" android:name="com.uc.deployment.UpgradeDeployService" android:process=":deploy" />
Quando esse serviço é iniciado, o navegador executa uma solicitação POST para
puds.ucweb.com/upgrade/index.xhtml , que pode ser percebida no tráfego algum tempo após o início. Em resposta, ele pode receber um comando para baixar uma atualização ou um novo módulo. No processo de análise, o servidor não deu esses comandos, mas notamos que, ao tentar abrir o PDF no navegador, ele faz uma segunda solicitação no endereço acima, após o qual baixa a biblioteca nativa. Para o ataque, decidimos usar esse recurso do UC Browser: a capacidade de abrir PDF usando uma biblioteca nativa, que não está no APK e que, se necessário, é baixada da Internet. É importante notar que, teoricamente, o UC Browser pode ser forçado a baixar algo sem a interação do usuário - se você der uma resposta corretamente formada a uma solicitação que é executada após o navegador iniciar. Porém, para isso, precisamos estudar o protocolo de interação com o servidor com mais detalhes, por isso decidimos que era mais fácil editar a resposta interceptada e substituir a biblioteca por trabalhar com PDF.
Portanto, quando um usuário deseja abrir um PDF diretamente em um navegador, as seguintes solicitações podem ser vistas no tráfego:

Primeiro, vem uma solicitação POST para
puds.ucweb.com/upgrade/index.xhtml , após o que
Baixe o arquivo com a biblioteca para visualizar os formatos PDF e Office É lógico supor que, na primeira solicitação, as informações sobre o sistema sejam transmitidas (pelo menos a arquitetura para fornecer a biblioteca necessária) e, em resposta, o navegador recebe algumas informações sobre a biblioteca que precisa ser baixada: endereço e, possivelmente, outra coisa. O problema é que essa solicitação está criptografada.
A própria biblioteca é compactada em ZIP e não criptografada.

Código de descriptografia de tráfego de pesquisa
Vamos tentar descriptografar a resposta do servidor. Examinamos o código da classe
com.uc.deployment.UpgradeDeployService : do método
onStartCommand, acesse
com.uc.deployment.bx e, a partir dele, para
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(); mb(v3_4); h v4_2 = new h(); mb(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.baacgetCpuArch())); v3_2.add(g.fs("cpu_arch", com.uc.baacPb())); String v4_3 = com.uc.baacPd(); 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.baacPc())); 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.baacJl()))); v3_2.add(g.fs("ram_1", String.valueOf(com.uc.baahPo()))); v3_2.add(g.fs("totalram", String.valueOf(com.uc.baahOL()))); 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 = gi(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.dci 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.dci$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); mb(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); }
Vemos aqui a formação da solicitação POST. Chamamos atenção para a criação de uma matriz de 16 bytes e seu preenchimento: 0x5F, 0, 0x1F, -50 (= 0xCE). Corresponde ao que vimos na solicitação acima.
Na mesma classe, você pode notar uma classe aninhada na qual existe outro método interessante:
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 = gj(arg11[2], v7); } if(v7 == null) { goto label_57; } if(v5 != 0) { v1_1 = gP(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 = gb(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"); } }
O método recebe uma matriz de bytes como entrada e verifica se o byte zero é 0x60 ou o terceiro byte é 0xD0 e o segundo byte é 1, 11 ou 0x1F. Nós olhamos para a resposta do servidor: zero byte - 0x60, segundo - 0x1F, terceiro - 0x60. Parece que precisamos. A julgar pelas linhas ("up_decrypt", por exemplo), um método deve ser chamado aqui que descriptografa a resposta do servidor.
Passamos para o método
gj . Observe que o byte no deslocamento 2 (ou seja, 0x1F no nosso caso) é transferido para ele como o primeiro argumento e a resposta do servidor sem
primeiros 16 bytes.
public static byte[] j(int arg1, byte[] arg2) { if(arg1 == 1) { arg2 = cc(arg2, c.adu); } else if(arg1 == 11) { arg2 = m.aF(arg2); } else if(arg1 != 0x1F) { } else { arg2 = EncryptHelper.decrypt(arg2); } return arg2; }
Obviamente, existe uma opção de algoritmo de descriptografia, e o mesmo byte, que em nossa
case for 0x1F, indica uma das três opções possíveis.
Continuamos a analisar o código. Após alguns saltos, entramos em um método com o nome falante
decryptBytesByKey .
Aqui, mais dois bytes são separados da nossa resposta e uma string é obtida deles. É claro que dessa maneira a chave é selecionada para descriptografar a mensagem.
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];
No futuro, observamos que, nesta fase, a chave ainda não foi obtida, mas apenas seu "identificador". Obter a chave é um pouco mais complicado.
No próximo método, mais dois são adicionados aos parâmetros existentes, e existem quatro deles: o número mágico 16, o identificador da chave, dados criptografados e uma string incompreensível (no nosso caso, vazia).
public final byte[] l(String keyId, byte[] encrypted) throws SecException { return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, ""); }
Após uma série de transições, chegamos ao método
staticBinarySafeDecryptNoB64 da interface
com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent . Não há classes no código principal do aplicativo que implementam essa interface. Essa classe está no arquivo
lib / armeabi-v7a / libsgmain.so , que na verdade não é .so, mas .jar. O método de seu interesse é implementado da seguinte forma:
package com.alibaba.wireless.security.ai;
Aqui, nossa lista de parâmetros é complementada por mais dois números inteiros: 2 e 0. A julgar por
para todos, 2 significa descriptografia, como no método
doFinal da classe de sistema
javax.crypto.Cipher . E tudo isso é transferido para um determinado roteador com o número 10601 - esse, aparentemente, é o número do comando.
Após a próxima cadeia de transição, encontramos uma classe que implementa a interface
IRouterComponent e o método
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); } }
E também a classe
JNICLibrary , na qual o método nativo
doCommandNative é
declarado :
package com.taobao.wireless.security.adapter; public class JNICLibrary { public static native Object doCommandNative(int arg0, Object[] arg1); }
Portanto, precisamos encontrar o método
doCommandNative no código nativo. E então a diversão começa.
Ofuscação do código da máquina
Há uma biblioteca nativa no arquivo
libsgmain.so (que na verdade é .jar e na qual encontramos uma pequena implementação de algumas interfaces de criptografia um pouco mais alta):
libsgmainso-6.4.36.so . Abra-o na IDA e obtenha várias caixas de diálogo com erros. O problema é que a tabela de cabeçalho da seção é inválida. Isso é feito especificamente para complicar a análise.

Mas também não é necessário: para carregar corretamente o arquivo ELF e analisá-lo, a tabela de cabeçalho do programa é suficiente. Portanto, simplesmente excluímos a tabela de seção, anulando os campos correspondentes no cabeçalho.

Abra novamente o arquivo no IDA.
Há duas maneiras de informar exatamente à máquina virtual Java onde, na biblioteca nativa, está localizada a implementação do método declarado no código Java como nativo. A primeira é fornecer um nome no formato
Java_package_name_ClassName_Method_name .
O segundo é registrá-lo ao carregar a biblioteca (na função
JNI_OnLoad )
chamando a função
RegisterNatives .
No nosso caso, se você usar o primeiro método, o nome deverá ser assim:
Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative .
Entre as funções exportadas, não há nenhuma, portanto, é necessário procurar a chamada
RegisterNatives .
Vamos para a função
JNI_OnLoad e vemos a seguinte imagem:

O que está acontecendo aqui? À primeira vista, o início e o fim da função são típicos da arquitetura ARM. A primeira instrução na pilha armazena o conteúdo dos registradores que a função usará em seu trabalho (neste caso, R0, R1 e R2), bem como o conteúdo do registrador LR, no qual o endereço de retorno da função está localizado. Com a última instrução, os registradores salvos são restaurados e o endereço de retorno é imediatamente colocado no registrador do PC - retornando assim da função. Mas se você olhar atentamente, notará que a penúltima instrução altera o endereço de retorno armazenado na pilha. Nós calculamos o que será depois
execução de código. Um determinado endereço 0xB130 é carregado em R1, 5 é subtraído, depois é transferido para R0 e 0x10 é adicionado a ele. Acontece 0xB13B. Assim, a IDA pensa que na última instrução há um retorno normal da função, mas, de fato, há uma transição para o endereço calculado 0xB13B.
Vale lembrar que os processadores ARM possuem dois modos e dois conjuntos de instruções: ARM e Thumb. O bit menos significativo do endereço informa ao processador qual conjunto de instruções está sendo usado. Ou seja, o endereço é realmente 0xB13A e a unidade no bit baixo indica o modo Thumb.
No início de cada função nesta biblioteca é adicionado um "adaptador" semelhante e
código de lixo. Além disso, não iremos nos aprofundar neles em detalhes - apenas lembramos
que o início real de quase todas as funções está um pouco mais longe.
Como não há transição explícita para 0xB13A no código, o próprio IDA não reconheceu que o código estava nesse local. Pelo mesmo motivo, ele não reconhece a maior parte do código da biblioteca como código, o que dificulta a análise. Dizemos à IDA que o código está aqui, e aqui está o resultado:

No 0xB144, a tabela começa claramente. E o sub_494C?

Quando essa função é chamada no registro LR, obtemos o endereço da tabela mencionada acima (0xB144). Em R0, o índice nesta tabela. Ou seja, um valor é retirado da tabela, adicionado ao LR e obtido
endereço para onde ir. Vamos tentar calculá-lo: 0xB144 + [0xB144 + 8 * 4] = 0xB144 + 0x120 = 0xB264. Vamos para o endereço recebido e vemos apenas algumas instruções úteis e, novamente, a transição para 0xB140:

Agora haverá uma mudança pelo deslocamento com o índice 0x20 da tabela.
A julgar pelo tamanho da tabela, existem muitas transições no código. Surge a questão de saber se é possível lidar de alguma forma com isso mais automaticamente, sem calcular manualmente os endereços. E os scripts e a capacidade de corrigir o código na AID vêm em nosso auxílio:
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:
Colocamos o cursor na linha 0xB26A, executamos o script e vemos a transição para 0xB4B0:

A IDA novamente não reconheceu este site como um código. Nós a ajudamos e vemos outra construção lá:

As instruções após o BLX não parecem muito significativas, são mais como algum tipo de viés. Observamos em sub_4964:

De fato, aqui o dword é obtido no endereço localizado em LR, adicionado a esse endereço, após o qual o valor no endereço recebido é obtido e enviado para a pilha. Além disso, 4 é adicionado ao LR, para que, após retornar da função, pule esse mesmo deslocamento. Em seguida, o comando POP {R1} extrai o valor recebido da pilha. Se você olhar para o endereço 0xB4BA + 0xEA = 0xB5A4, poderá ver algo semelhante à tabela de endereços:

Para corrigir esse design, você precisa obter dois parâmetros do código: o deslocamento e o número do registro no qual deseja colocar o resultado. Para cada registro possível, você deverá preparar um código com antecedência.
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
Colocamos o cursor no início da construção que queremos substituir - 0xB4B2 - e executamos o script:

Além das construções já mencionadas no código, aqui estão também:

Como no caso anterior, após a instrução BLX, há um deslocamento:

Tomamos o deslocamento para o endereço da LR, adicionamos à LR e vamos lá. 0x72044 + 0xC = 0x72050. O script para este design é muito simples:
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:
O resultado do script:

Depois que tudo estiver corrigido na função, você poderá apontar o IDA para o seu verdadeiro começo. Ele coletará todo o código da função em partes e poderá ser descompilado usando HexRays.
Decodificando strings
Aprendemos como lidar com a ofuscação de código de máquina na biblioteca
libsgmainso-6.4.36.so do UC Browser e obtivemos o código para a função
JNI_OnLoad .
int __fastcall real_JNI_OnLoad(JavaVM *vm) { int result;
Considere cuidadosamente as seguintes linhas:
sub_73E24(&unk_83EA6, &v6, 49); clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
A função
sub_73E24 descriptografa explicitamente o nome da classe. , , . , , . .
FindClass , . , — . , , . ,
sub_73E24. int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size) { int v4;
sub_7AF78 ( ). :
«DcO/lcK+h?m3c*q@» ( , ), — . ,
sub_6115C . 3. , .
int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2) { int v3;
switch , 3. case 3:
sub_6364C , , . . .
sub_6364C , RC4.
. . :
com/taobao/wireless/security/adapter/JNICLibrary . Ótimo! .
RegisterNatives ,
doCommandNative . ,
JNI_OnLoad, sub_B7B0 :
int __fastcall sub_B7F6(JNIEnv *env, jclass clazz) { char signature[41];
De fato, um método nativo chamado doCommandNative é registrado aqui . Agora sabemos o endereço dele. Vamos ver o que ele faz. int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args) { int v5;
, , . 10601.
, :
command / 10000 ,
command % 10000 / 100 command % 10 , . ., , 1, 6 1. ,
JNIEnv , , . ( N1, N2 N3) .
:
JNI_OnLoad .
. . — . , , , ( , ).
, : 0x5F1AC. : UC Browser .
, Java-,
0x4D070. .
R7 R4 :

R11:

, :

, R4. 230 .
O que fazer sobre isso? IDA, switch: Edit -> Other -> Specify switch idiom.

. , ,
sub_6115C :

switch, case 3 RC4. ,
doCommandNative . ,
magicInt 16. case – , .

AES!
, : , , , ( AES). -
sub_6115C , , , .
, Android Studio, , , , , , .
UC Browser «». , , . :) , , , . . .
:

ARM R0-R3, , — . LR . , , . , , PUSH.W {R0-R10,LR}. R7 , .
fopen /data/local/tmp/aes «ab»,
. . . R0 , R1 — . , . , , .
fopen .
aes int . ,
fwrite .

, , .

,
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
, , , . NullPointerException, . . null.
, : «META-INF/» ".RSA". , . . , , , . , «META-INF/» «BLABLINF/», APK .
, , , . Bingo! !
MitM
, . CBC.

URL , - MD5, «extract_unzipsize» . : MD5 , . . , , Intent «PWNED!». :
puds.ucweb.com/upgrade/index.xhtml . MD5 ( ), .
, . , -
. , :

LEB128. , , , .
… – ! :) .
https://www.youtube.com/watch?v=Nfns7uH03J8UC Browser, . , . — , , , .
UC Browser , , - . . , , , . 27
UC Browser 12.10.9.1193, HTTPS:
puds.ucweb.com/upgrade/index.xhtml .
, «» PDF «, - !». PDF , , Google Play.