Procurando vulnerabilidades no navegador UC

Procurando vulnerabilidades no navegador UC

Introdução

No final de março nós relatado, que descobriram uma capacidade oculta de carregar e executar código não verificado no UC Browser. Hoje veremos 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: era instalado nos dispositivos dos usuários por meio de malware, distribuído de vários sites sob o disfarce de arquivos de vídeo (ou seja, os usuários pensavam que estavam baixando, por exemplo, um vídeo pornô, mas em vez disso, recebi um APK com este navegador), usei banners assustadores com mensagens de que o navegador estava desatualizado, vulnerável e coisas assim. No grupo oficial do UC Browser no VK existe tópico, em que os usuários podem reclamar de publicidade injusta, há muitos exemplos aí. Em 2016 houve ainda publicidade em vídeo em russo (sim, publicidade de um navegador com bloqueio de anúncios).

No momento em que este artigo foi escrito, o UC Browser tinha mais de 500 milhões de instalações no Google Play. Isso é impressionante – apenas o Google Chrome tem mais. Entre os comentários você pode ver muitas reclamações sobre publicidade e redirecionamentos para alguns aplicativos do Google Play. Este foi o motivo da nossa pesquisa: decidimos ver se o UC Browser estava fazendo algo ruim. E descobriu-se que ele faz!

No código do aplicativo, foi descoberta a capacidade de baixar e executar código executável, o que é contrário às regras de publicação de aplicações no Google Play. Além de o UC Browser baixar código executável, ele o faz de forma insegura, que pode ser usada para realizar um ataque MitM. Vamos ver se conseguimos realizar tal ataque.

Tudo o que está escrito abaixo é relevante para a versão do UC Browser que estava disponível no Google Play no momento do estudo:

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

Vetor de ataque

No manifesto do UC Browser você pode encontrar um serviço com um nome autoexplicativo com.uc.deployment.UpgradeDeployService.

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

Quando este serviço é iniciado, o navegador faz uma solicitação POST para puds.ucweb.com/upgrade/index.xhtml, que pode ser visto no trânsito algum tempo após a largada. Em resposta, ele poderá receber um comando para baixar alguma atualização ou novo módulo. Durante a análise, o servidor não deu tais comandos, mas percebemos que ao tentar abrir um PDF no navegador, ele faz uma segunda solicitação para o endereço especificado acima, após o qual baixa a biblioteca nativa. Para realizar o ataque, decidimos utilizar este recurso do UC Browser: a possibilidade de abrir PDF usando uma biblioteca nativa, que não está no APK e que ele baixa da Internet se necessário. É importante notar que, teoricamente, o UC Browser pode ser forçado a baixar algo sem interação do usuário - se você fornecer uma resposta bem formada a uma solicitação que é executada após o lançamento do navegador. Mas para isso precisamos estudar mais detalhadamente o protocolo de interação com o servidor, por isso decidimos que seria mais fácil editar a resposta interceptada e substituir a biblioteca para trabalhar com PDF.

Assim, quando um usuário deseja abrir um PDF diretamente no navegador, as seguintes solicitações podem ser visualizadas no tráfego:

Procurando vulnerabilidades no navegador UC

Primeiro, há uma solicitação POST para puds.ucweb.com/upgrade/index.xhtmldepois disso
É baixado um arquivo com uma biblioteca para visualização de formatos PDF e Office. É lógico supor que a primeira solicitação transmite informações sobre o sistema (pelo menos a arquitetura para fornecer a biblioteca necessária), e em resposta a ela o navegador recebe algumas informações sobre a biblioteca que precisa ser baixada: o endereço e, possivelmente , algo mais. O problema é que essa solicitação é criptografada.

Solicitar fragmento

Fragmento de resposta

Procurando vulnerabilidades no navegador UC

Procurando vulnerabilidades no navegador UC

A biblioteca em si é compactada em ZIP e não é criptografada.

Procurando vulnerabilidades no navegador UC

Procure o código de descriptografia de tráfego

Vamos tentar decifrar a resposta do servidor. Vejamos o código da classe com.uc.deployment.UpgradeDeployService: do método onStartCommand Vá para com.uc.deployment.bx, e 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();
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);
}

Vemos a formação de uma solicitação POST aqui. Prestamos atenção na criação de um array de 16 bytes e seu preenchimento: 0x5F, 0, 0x1F, -50 (=0xCE). Coincide com o que vimos na solicitação acima.

Na mesma classe você pode ver uma classe aninhada que possui 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 = 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");
}
}

O método pega 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. Observamos a resposta do servidor: o byte zero é 0x60, o segundo é 0x1F, o terceiro é 0x60. Parece o que precisamos. A julgar pelas linhas (“up_decrypt”, por exemplo), aqui deve ser chamado um método que irá descriptografar a resposta do servidor.
Vamos passar para o método gj. Observe que o primeiro argumento é o byte no deslocamento 2 (ou seja, 0x1F no nosso caso), e o segundo é a resposta do servidor sem
primeiros 16 bytes.

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

Obviamente, aqui selecionamos um algoritmo de descriptografia, e o mesmo byte que está em nosso
caso igual a 0x1F, denota uma das três opções possíveis.

Continuamos analisando o código. Depois de alguns saltos, nos encontramos em um método com um nome autoexplicativo decryptBytesByKey.

Aqui mais dois bytes são separados de nossa resposta e uma string é obtida deles. É claro que desta forma é selecionada a chave 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];  // 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;
}

Olhando para o futuro, notamos que nesta fase ainda não obtemos uma chave, mas apenas o seu “identificador”. Conseguir a chave é um pouco mais complicado.

No próximo método, mais dois parâmetros são adicionados aos existentes, perfazendo quatro deles: o número mágico 16, o identificador da chave, os 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 da aplicação que implementem esta interface. Existe tal classe no arquivo lib/armeabi-v7a/libsgmain.so, que na verdade não é um .so, mas um .jar. O método que nos interessa é implementado da seguinte forma:

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

Aqui nossa lista de parâmetros é complementada com mais dois inteiros: 2 e 0. A julgar por
tudo, 2 significa descriptografia, como no método doFinal classe de sistema javax.crypto.Cipher. E tudo isso é transferido para um determinado roteador com o número 10601 - aparentemente esse é o número do comando.

Após a próxima cadeia de transições, encontramos uma classe que implementa a interface IRRouterComponent e 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 aula Biblioteca JNIC, em que o método nativo é declarado doCommandNative:

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

Isso significa que precisamos encontrar um método no código nativo doCommandNative. E é aqui que a diversão começa.

Ofuscação de código de máquina

No arquivo libsgmain.so (que na verdade é um .jar e no qual encontramos a implementação de algumas interfaces relacionadas à criptografia logo acima) existe uma biblioteca nativa: libsgmainso-6.4.36.so. Abrimos no IDA e obtemos várias caixas de diálogo com erros. O problema é que a tabela de cabeçalho da seção é inválida. Isso é feito propositalmente para complicar a análise.

Procurando vulnerabilidades no navegador UC

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

Procurando vulnerabilidades no navegador UC

Abra o arquivo no IDA novamente.

Existem duas maneiras de informar à máquina virtual Java onde exatamente na biblioteca nativa está localizada a implementação de um método declarado no código Java como nativo. A primeira é dar-lhe um nome de espécie Java_package_name_ClassName_MethodName.

A segunda é registrá-lo ao carregar a biblioteca (na função JNI_OnLoad)
usando uma chamada de função RegistrarNativos.

No nosso caso, se usarmos o primeiro método, o nome deverá ser assim: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Não existe tal função entre as funções exportadas, o que significa que você precisa procurar uma chamada RegistrarNativos.
Vamos para a função JNI_OnLoad e vemos esta foto:

Procurando vulnerabilidades no navegador UC

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 da pilha armazena o conteúdo dos registradores que a função utilizará em seu funcionamento (neste caso, R0, R1 e R2), bem como o conteúdo do registrador LR, que contém o endereço de retorno da função . A última instrução restaura os registros salvos, e o endereço de retorno é imediatamente colocado no registro do PC - retornando assim da função. Mas se você olhar com atenção, notará que a penúltima instrução altera o endereço de retorno armazenado na pilha. Vamos calcular como será depois
execução de código. Um determinado endereço 1xB0 é carregado em R130, 5 é subtraído dele, então é transferido para R0 e 0x10 é adicionado a ele. Acontece 0xB13B. Assim, o IDA pensa que a última instrução é um retorno normal de função, mas na verdade ela está indo para o endereço calculado 0xB13B.

Vale lembrar aqui 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 é na verdade 0xB13A e um no bit menos significativo indica o modo Thumb.

Um “adaptador” semelhante foi adicionado ao início de cada função nesta biblioteca e
código lixo. Não vamos nos alongar mais sobre eles - apenas nos lembramos
que o verdadeiro início de quase todas as funções está um pouco mais distante.

Como o código não salta explicitamente para 0xB13A, o próprio IDA não reconheceu que o código estava localizado neste local. Pela mesma razão, ele não reconhece a maior parte do código da biblioteca como código, o que dificulta um pouco a análise. Dizemos ao IDA que este é o código e é isso que acontece:

Procurando vulnerabilidades no navegador UC

A tabela começa claramente em 0xB144. O que há em sub_494C?

Procurando vulnerabilidades no navegador UC

Ao chamar esta função no registrador LR, obtemos o endereço da tabela mencionada anteriormente (0xB144). Em R0 - índice desta tabela. Ou seja, o valor é retirado da tabela, somado ao LR e o resultado é
o endereço para onde ir. Vamos tentar calculá-lo: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vamos ao endereço recebido e vemos literalmente algumas instruções úteis e novamente vamos para 0xB140:

Procurando vulnerabilidades no navegador UC

Agora haverá uma transição no deslocamento com índice 0x20 da tabela.

A julgar pelo tamanho da tabela, haverá muitas dessas transições no código. Surge a questão de saber se é possível lidar com isso de alguma forma de forma mais automática, sem calcular manualmente os endereços. E os scripts e a capacidade de corrigir código no IDA 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: #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"

Coloque o cursor na linha 0xB26A, execute o script e veja a transição para 0xB4B0:

Procurando vulnerabilidades no navegador UC

A IDA novamente não reconheceu esta área como um código. Nós a ajudamos e vemos outro desenho ali:

Procurando vulnerabilidades no navegador UC

As instruções após o BLX não parecem fazer muito sentido, é mais uma espécie de deslocamento. Vejamos sub_4964:

Procurando vulnerabilidades no navegador UC

E, de fato, aqui um dword é obtido no endereço que está em LR, adicionado a esse endereço, após o qual o valor no endereço resultante é obtido e colocado na pilha. Além disso, 4 é adicionado a LR para que após retornar da função, esse mesmo deslocamento seja ignorado. Depois disso, o comando POP {R1} retira o valor resultante da pilha. Se você observar o que está localizado no endereço 0xB4BA + 0xEA = 0xB5A4, verá algo semelhante a uma tabela de endereços:

Procurando vulnerabilidades no navegador UC

Para corrigir esse design, você precisará 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 antecipadamente um trecho de código.

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"

Colocamos o cursor no início da estrutura que queremos substituir - 0xB4B2 - e executamos o script:

Procurando vulnerabilidades no navegador UC

Além das estruturas já mencionadas, o código também contém o seguinte:

Procurando vulnerabilidades no navegador UC

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

Procurando vulnerabilidades no navegador UC

Pegamos o deslocamento para o endereço de LR, adicionamos ao LR e vamos para lá. 0x72044 + 0xC = 0x72050. O script para este design é bastante 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: #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"

Resultado da execução do script:

Procurando vulnerabilidades no navegador UC

Depois que tudo estiver corrigido na função, você poderá apontar o IDA para seu início real. Ele reunirá todo o código da função e poderá ser descompilado usando HexRays.

Decodificando strings

Aprendemos a lidar com a ofuscação do código de máquina na biblioteca libsgmainso-6.4.36.so do UC Browser e recebeu o código de função JNI_OnLoad.

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

Vamos dar uma olhada mais de perto nas seguintes linhas:

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

Em função sub_73E24 o nome da classe está claramente sendo descriptografado. Como parâmetros para esta função, são passados ​​​​um ponteiro para dados semelhantes aos dados criptografados, um determinado buffer e um número. Obviamente, após chamar a função, haverá uma linha descriptografada no buffer, pois ela é passada para a função EncontrarClass, que leva o nome da classe como segundo parâmetro. Portanto, o número é o tamanho do buffer ou o comprimento da linha. Vamos tentar decifrar o nome da classe, ele deve nos dizer se estamos indo na direção certa. Vamos dar uma olhada mais de perto no que acontece em sub_73E24.

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

Função sub_7AF78 cria uma instância de um contêiner para matrizes de bytes do tamanho especificado (não nos deteremos nesses contêineres em detalhes). Aqui são criados dois desses contêineres: um contém a linha "DcO/lcK+h?m3c*q@" (é fácil adivinhar que se trata de uma chave), a outra contém dados criptografados. A seguir, ambos os objetos são colocados em uma determinada estrutura, que é passada para a função sub_6115C. Vamos marcar também nesta estrutura um campo com o valor 3. Vamos ver o que acontece a seguir com esta estrutura.

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

O parâmetro switch é um campo de estrutura ao qual foi atribuído anteriormente o valor 3. Veja o caso 3: para a função sub_6364C os parâmetros são passados ​​​​da estrutura que foi adicionada lá na função anterior, ou seja, a chave e os dados criptografados. Se você olhar de perto sub_6364C, você pode reconhecer o algoritmo RC4 nele.

Temos um algoritmo e uma chave. Vamos tentar decifrar o nome da classe. Aqui está o que aconteceu: com/taobao/wireless/security/adapter/JNICLibrary. Ótimo! Estamos no caminho certo.

Árvore de comando

Agora precisamos encontrar um desafio RegistrarNativos, o que nos indicará a função doCommandNative. Vejamos as funções chamadas de JNI_OnLoad, e encontramos isso em sub_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;
}

E de fato, um método nativo com o nome está registrado aqui doCommandNative. 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; // 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;
}

Pelo nome você pode adivinhar que aqui está o ponto de entrada de todas as funções que os desenvolvedores decidiram transferir para a biblioteca nativa. Estamos interessados ​​na função número 10601.

Você pode ver no código que o comando number produz três números: comando/10000, comando% 10000/100 и comando% 10, ou seja, no nosso caso, 1, 6 e 1. Esses três números, bem como um ponteiro para JNIEnv e os argumentos passados ​​para a função são adicionados a uma estrutura e repassados. Usando os três números obtidos (vamos denotá-los N1, N2 e N3), uma árvore de comandos é construída.

Algo assim:

Procurando vulnerabilidades no navegador UC

A árvore é preenchida dinamicamente em JNI_OnLoad.
Três números codificam o caminho na árvore. Cada folha da árvore contém o endereço da função correspondente. A chave está no nó pai. Encontrar o local no código onde a função que precisamos é adicionada à árvore não é difícil se você entender todas as estruturas usadas (não as descrevemos para não sobrecarregar um artigo já bastante extenso).

Mais ofuscação

Recebemos o endereço da função que deve descriptografar o tráfego: 0x5F1AC. Mas é muito cedo para nos alegrarmos: os desenvolvedores do UC Browser prepararam outra surpresa para nós.

Após receber os parâmetros do array que foi formado no código Java, obtemos
para a função no endereço 0x4D070. E aqui nos espera outro tipo de ofuscação de código.

Colocamos dois índices em R7 e R4:

Procurando vulnerabilidades no navegador UC

Mudamos o primeiro índice para R11:

Procurando vulnerabilidades no navegador UC

Para obter um endereço de uma tabela, use um índice:

Procurando vulnerabilidades no navegador UC

Após ir para o primeiro endereço, é utilizado o segundo índice, que está em R4. Existem 230 elementos na tabela.

O que fazer sobre isso? Você pode dizer ao IDA que se trata de uma opção: Editar -> Outro -> Especificar o idioma da opção.

Procurando vulnerabilidades no navegador UC

O código resultante é terrível. Mas, percorrendo sua selva, você pode notar uma chamada para uma função que já conhecemos sub_6115C:

Procurando vulnerabilidades no navegador UC

Houve uma troca em que no caso 3 houve uma descriptografia utilizando o algoritmo RC4. E neste caso, a estrutura passada para a função é preenchida a partir dos parâmetros passados ​​para doCommandNative. Vamos lembrar o que tínhamos lá magiaInt com o valor 16. Observamos o caso correspondente - e após várias transições encontramos o código pelo qual o algoritmo pode ser identificado.

Procurando vulnerabilidades no navegador UC

Isso é AES!

O algoritmo existe, falta apenas obter seus parâmetros: modo, chave e, possivelmente, o vetor de inicialização (sua presença depende do modo de operação do algoritmo AES). A estrutura com eles deve ser formada em algum lugar antes da chamada da função sub_6115C, mas esta parte do código é especialmente bem ofuscada, então surge a ideia de corrigir o código para que todos os parâmetros da função de descriptografia sejam despejados em um arquivo.

Patch

Para não escrever todo o código do patch em linguagem assembly manualmente, você pode iniciar o Android Studio, escrever uma função lá que receba os mesmos parâmetros de entrada da nossa função de descriptografia e gravar em um arquivo, depois copiar e colar o código que o compilador irá gerar.

Nossos amigos da equipe do UC Browser também cuidaram da comodidade de adicionar código. Lembremos que no início de cada função temos um código lixo que pode ser facilmente substituído por qualquer outro. Muito conveniente 🙂 Porém, no início da função alvo não há espaço suficiente para o código que salva todos os parâmetros em um arquivo. Tive que dividi-lo em partes e usar blocos de lixo de funções vizinhas. Foram quatro partes no total.

Первая часть:

Procurando vulnerabilidades no navegador UC

Na arquitetura ARM, os primeiros quatro parâmetros de função são passados ​​pelos registradores R0-R3, o restante, se houver, é passado pela pilha. O registrador LR carrega o endereço de retorno. Tudo isso precisa ser salvo para que a função possa funcionar depois de despejarmos seus parâmetros. Também precisamos salvar todos os registradores que utilizaremos no processo, então fazemos PUSH.W {R0-R10,LR}. Em R7 obtemos o endereço da lista de parâmetros passados ​​para a função através da pilha.

Usando a função abrir vamos abrir o arquivo /dados/local/tmp/aes no modo "ab"
ou seja, para adição. Em R0 carregamos o endereço do nome do arquivo, em R1 - o endereço da linha que indica o modo. E aqui termina o código lixo, então passamos para a próxima função. Para que continue funcionando, colocamos no início a transição para o código real da função, contornando o lixo, e em vez do lixo adicionamos uma continuação do patch.

Procurando vulnerabilidades no navegador UC

Chamando abrir.

Os três primeiros parâmetros da função Aes tem tipo int. Como salvamos os registradores na pilha no início, podemos simplesmente passar a função escrever seus endereços na pilha.

Procurando vulnerabilidades no navegador UC

A seguir, temos três estruturas que contêm o tamanho dos dados e um ponteiro para os dados da chave, vetor de inicialização e dados criptografados.

Procurando vulnerabilidades no navegador UC

Ao final feche o arquivo, restaure os registros e transfira o controle para a função real Aes.

Coletamos um APK com uma biblioteca corrigida, assinamos, carregamos no dispositivo/emulador e iniciamos. Vemos que nosso dump está sendo criado e muitos dados estão sendo gravados nele. O navegador usa criptografia não apenas para tráfego, e toda criptografia passa pela função em questão. Mas, por algum motivo, os dados necessários não estão lá e a solicitação necessária não está visível no tráfego. Para não esperar até que o UC Browser se digne a fazer a solicitação necessária, vamos pegar a resposta criptografada do servidor recebida anteriormente e corrigir o aplicativo novamente: adicionar a descriptografia ao onCreate da atividade principal.

    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

Montamos, assinamos, instalamos, lançamos. Recebemos uma NullPointerException porque o método retornou nulo.

Durante uma análise mais aprofundada do código, foi descoberta uma função que decifra linhas interessantes: “META-INF/” e “.RSA”. Parece que o aplicativo está verificando seu certificado. Ou até mesmo gera chaves a partir dele. Eu realmente não quero lidar com o que está acontecendo com o certificado, então vamos apenas inserir o certificado correto. Vamos corrigir a linha criptografada para que em vez de “META-INF/” obtenhamos “BLABLINF/”, crie uma pasta com esse nome no APK e adicione o certificado do navegador esquilo lá.

Montamos, assinamos, instalamos, lançamos. Bingo! Nós temos a chave!

Com M

Recebemos uma chave e um vetor de inicialização igual à chave. Vamos tentar descriptografar a resposta do servidor no modo CBC.

Procurando vulnerabilidades no navegador UC

Vemos o URL do arquivo, algo semelhante ao MD5, “extract_unzipsize” e um número. Verificamos: o MD5 do arquivo é o mesmo, o tamanho da biblioteca descompactada é o mesmo. Estamos tentando corrigir esta biblioteca e fornecê-la ao navegador. Para mostrar que nossa biblioteca corrigida foi carregada, lançaremos um Intent para criar um SMS com o texto “PWNED!” Substituiremos duas respostas do servidor: puds.ucweb.com/upgrade/index.xhtml e para baixar o arquivo. No primeiro substituímos o MD5 (o tamanho não muda após a descompactação), no segundo damos o arquivo com a biblioteca corrigida.

O navegador tenta baixar o arquivo várias vezes, após o que ocorre um erro. Aparentemente algo
ele não gosta. Como resultado da análise desse formato obscuro, descobriu-se que o servidor também transmite o tamanho do arquivo:

Procurando vulnerabilidades no navegador UC

Está codificado em LEB128. Após o patch, o tamanho do arquivo com a biblioteca mudou um pouco, então o navegador considerou que o arquivo foi baixado torto e após várias tentativas apresentou um erro.

Ajustamos o tamanho do arquivo... E – vitória! 🙂 O resultado está no vídeo.

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

Consequências e reação do desenvolvedor

Da mesma forma, os hackers poderiam usar o recurso inseguro do UC Browser para distribuir e executar bibliotecas maliciosas. Essas bibliotecas funcionarão no contexto do navegador, portanto receberão todas as permissões do sistema. Como resultado, a capacidade de exibir janelas de phishing, bem como o acesso aos arquivos de trabalho do esquilo chinês laranja, incluindo logins, senhas e cookies armazenados no banco de dados.

Entramos em contato com os desenvolvedores do UC Browser e informamos sobre o problema que encontramos, tentamos apontar a vulnerabilidade e seu perigo, mas eles não discutiram nada conosco. Enquanto isso, o navegador continuou a exibir seu recurso perigoso à vista de todos. Mas assim que revelamos os detalhes da vulnerabilidade, não foi mais possível ignorá-la como antes. 27 de março foi
foi lançada uma nova versão do UC Browser 12.10.9.1193, que acessava o servidor via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Além disso, após a “conserção” e até o momento da redação deste artigo, a tentativa de abrir um PDF em um navegador resultava em uma mensagem de erro com o texto “Ops, algo deu errado!” Uma solicitação ao servidor não foi feita ao tentar abrir um PDF, mas uma solicitação foi feita quando o navegador foi iniciado, o que sugere a capacidade contínua de baixar código executável, violando as regras do Google Play.

Fonte: habr.com

Adicionar um comentário