Anchored
6 minutos de lectura
Se nos proporciona un archivo APK (Manager.apk
) y un archivo README.txt
:
1. Install this application in an API Level 29 or earlier (i.e. Android 10.0 (Google Play)).
1. Install this application in a non-rooted device (i.e. In Android Studio AVD Manager select an image that includes (Google Play)).
Ingeniería inversa
Lo primero que podemos hacer es descompilar el archivo APK y leer código fuente en Java.
Descomilación de APK
Si cargamos el APK en www.javadecompilers.com y seleccione “APK decompiler”, veremos este código para MainActivity.java
(sources/com/example/anchored/MainActivity.java
), donde he comentado algunas partes que no son relevantes:
package com.example.anchored;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import d.h;
import java.util.HashMap;
import java.util.Map;
import v0.p;
import v0.r;
import w0.j;
import w0.l;
public class MainActivity extends h {
/* renamed from: o reason: collision with root package name */
public Button f1944o;
/* renamed from: p reason: collision with root package name */
public TextView f1945p;
public class a implements View.OnClickListener {
/* renamed from: com.example.anchored.MainActivity$a$a reason: collision with other inner class name */
public class C0012a implements p.b<String> {
public C0012a() {
}
public void a(Object obj) {
try {
MainActivity.this.f1945p.setText("Thank you for requesting early access.");
} catch (Exception e2) {
e2.printStackTrace();
MainActivity.this.f1945p.setText("Thank you for requesting early access.");
}
}
}
public class b implements p.a {
public b() {
}
public void a(r rVar) {
MainActivity.this.f1945p.setText("Thank you for requesting early access.");
}
}
public class c extends j {
public c(int i2, String str, p.b bVar, p.a aVar) {
super(i2, str, bVar, aVar);
}
public Map<String, String> g() {
HashMap hashMap = new HashMap();
hashMap.put("Content-Type", "application/x-www-form-urlencoded");
return hashMap;
}
public Map<String, String> h() {
HashMap hashMap = new HashMap();
hashMap.put(MainActivity.this.prp(), MainActivity.this.s());
hashMap.put(MainActivity.this.mrm(), MainActivity.this.frf());
return hashMap;
}
}
public a() {
}
public void onClick(View view) {
MainActivity.this.f1945p.setText("Thank you for requesting early access.");
l.a(MainActivity.this).a(new c(i2:1, str:"https://anchored.com:4443/test.php", new C0012a(), new b()));
}
}
public class b implements View.OnClickListener { /* ... */ }
public class c implements View.OnClickListener { /* ... */ }
public class d implements View.OnClickListener { /* ... */ }
static {
System.loadLibrary("anchored");
}
public native int c8();
public native String frf();
public native String mrm();
/* JADX WARNING: Removed duplicated region for block: B:106:0x025b */
/* ... */
/* Code decompiled incorrectly, please refer to instructions dump. */
public void onCreate(android.os.Bundle r20) { /* ... */ }
public native String prp();
public String s() {
return ((EditText) findViewById(R.id.editTextTextEmailAddress)).getText().toString();
}
}
Observe que hay algunas funciones native
(prp
, frf
, mrm
, c8
), que provienen de una librería en libs/x86_64/libanchored.so
. Estas funciones se utilizan para crear un HashMap
:
public Map<String, String> g() {
HashMap hashMap = new HashMap();
hashMap.put("Content-Type", "application/x-www-form-urlencoded");
return hashMap;
}
public Map<String, String> h() {
HashMap hashMap = new HashMap();
hashMap.put(MainActivity.this.prp(), MainActivity.this.s());
hashMap.put(MainActivity.this.mrm(), MainActivity.this.frf());
return hashMap;
}
}
El método s
simplemente toma una dirección de correo electrónico de la página inicial. Obsérvese que hay una cadena con una URL: https://anchored.com:4443/test.php
, y otro HashMap
con una cabecera HTTP:
public Map<String, String> g() {
HashMap hashMap = new HashMap();
hashMap.put("Content-Type", "application/x-www-form-urlencoded");
return hashMap;
}
Por tanto, podemos deducir que el resultado de uno prp
, mrm
o frf
contendrá la flag. Probablemente, es frf
, porque prp
podría dar algo como email
, y mrm
algo como flag
ya que se usan como claves del HashMap
, no valores (de hecho, si lo miramos, prp
devuelve "mail"
y mrm
devuelve "msg"
).
Descompilación de librería
Entonces, si cargamos libanchored.so
en Ghidra, veremos esta función para frf
(Java_com_example_anchored_MainActivity_frf
):
undefined8 Java_com_example_anchored_MainActivity_frf(long *param_1) {
byte bVar1;
byte bVar2;
undefined *puVar3;
undefined8 uVar4;
byte *pbVar5;
ulong uVar6;
byte *pbVar7;
ulong uVar8;
ulong uVar9;
long lVar10;
long in_FS_OFFSET;
undefined local_1b8[8];
ulong uStack_1b0;
undefined *local_1a8;
byte *local_198[14];
undefined4 local_128;
undefined4 uStack_124;
undefined4 uStack_120;
undefined4 uStack_11c;
undefined *local_118;
undefined1 *puStack_110;
undefined *local_108;
undefined1 *puStack_100;
undefined4 local_f8;
undefined4 uStack_f4;
undefined4 uStack_f0;
undefined4 uStack_ec;
byte *local_e8 [22];
long local_38;
local_38 = *(long *) (in_FS_OFFSET + 0x28);
_Z14encryptDecryptv();
_local_1b8 = ZEXT816(0);
local_1a8 = NULL;
pbVar5 = &DAT_0012d0c9;
local_e8[0] = &DAT_0012d0c9;
local_e8[1] = &DAT_0012d0b5;
local_e8[2] = &DAT_0012d0cb;
local_e8[3] = &DAT_0012d0cd;
local_e8[4] = &DAT_0012d0c9;
local_e8[5] = &DAT_0012d0cf;
local_e8[6] = &DAT_0012d0d1;
local_e8[7] = &DAT_0012d0d3;
local_e8[8] = &DAT_0012d0d5;
local_e8[9] = &DAT_0012d097;
local_e8[10] = &DAT_0012d0d7;
local_e8[11] = &DAT_0012d0b7;
local_e8[12] = &DAT_0012d0d9;
local_e8[13] = &DAT_0012d0cb;
local_e8[14] = &DAT_0012d0db;
local_e8[15] = &DAT_0012d0dd;
local_e8[16] = &DAT_0012d0df;
local_e8[17] = &DAT_0012d0d7;
local_e8[18] = &DAT_0012d0c9;
local_e8[19] = &DAT_0012d0cb;
local_e8[20] = &DAT_0012d0b7;
local_e8[21] = &DAT_0012d0cf;
pbVar7 = &DAT_0012d0e1;
local_198[0] = &DAT_0012d0e1;
local_198[1] = &DAT_0012d091;
local_198[2] = &DAT_0012d0e1;
local_198[3] = &DAT_0012d091;
local_198[4] = &DAT_0012d0e1;
local_198[5] = &DAT_0012d091;
local_198[6] = &DAT_0012d091;
local_198[7] = &DAT_0012d097;
local_198[8] = &DAT_0012d091;
local_198[9] = &DAT_0012d0e1;
local_198[10] = &DAT_0012d0e1;
local_198[11] = &DAT_0012d091;
local_198[12] = &DAT_0012d095;
local_198[13] = &DAT_0012d0e1;
local_128 = 0x12d091;
uStack_124 = 0;
uStack_120 = 0x12d091;
uStack_11c = 0;
local_118 = &DAT_0012d095;
puStack_110 = &DAT_0012d0e1;
local_108 = &DAT_0012d0e3;
puStack_100 = &DAT_0012d0e1;
local_f8 = 0x12d091;
uStack_f4 = 0;
uStack_f0 = 0x12d091;
uStack_ec = 0;
lVar10 = 1;
uVar9 = 0;
do {
bVar2 = *pbVar7;
bVar1 = *pbVar5;
if ((uVar9 & 1) == 0) {
uVar8 = uVar9 >> 1;
uVar6 = 0x16;
if (uVar8 == 0x16) goto LAB_0010f581;
LAB_0010f552:
if ((uVar9 & 1) != 0) goto LAB_0010f558;
LAB_0010f5a6:
local_1b8[0] = (char) uVar8 * '\x02' + '\x02';
puVar3 = local_1b8 + 1;
} else {
uVar6 = ((ulong)local_1b8 & 0xfffffffffffffffe) - 1;
uVar8 = uStack_1b0;
if (uStack_1b0 != uVar6) goto LAB_0010f552;
LAB_0010f581:
// try { // try from 0010f581 to 0010f59e has its CatchHandler @ 0010f63a
_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm(local_1b8, uVar6, 1, uVar6, uVar6, 0, 0);
if ((_local_1b8 & (undefined[16]) 0x1) == (undefined[16]) 0x0) goto LAB_0010f5a6;
LAB_0010f558:
uStack_1b0 = uVar8 + 1;
puVar3 = local_1a8;
}
puVar3[uVar8] = bVar2 ^ bVar1;
puVar3[uVar8 + 1] = 0;
if (lVar10 == 0x16) {
puVar3 = local_1b8 + 1;
if ((_local_1b8 & (undefined[16]) 0x1) != (undefined[16]) 0x0) {
puVar3 = local_1a8;
}
// try { // try from 0010f5eb to 0010f5f6 has its CatchHandler @ 0010f638
uVar4 = (**(code **) (*param_1 + 0x538))(param_1, puVar3);
if ((_local_1b8 & (undefined [16])0x1) != (undefined [16])0x0) {
_ZdlPv(local_1a8);
}
if (*(long *) (in_FS_OFFSET + 0x28) == local_38) {
return uVar4;
}
// WARNING: Subroutine does not return
__stack_chk_fail();
}
pbVar5 = local_e8[lVar10];
pbVar7 = local_198[lVar10];
uVar9 = (ulong) local_1b8[0];
lVar10 = lVar10 + 1;
} while (true);
}
El código es muy feo, pero el mecanismo de cifrado es solo XOR, podemos notarlo aquí:
// ...
lVar10 = 1;
uVar9 = 0;
do {
bVar2 = *pbVar7;
bVar1 = *pbVar5;
if ((uVar9 & 1) == 0) {
uVar8 = uVar9 >> 1;
uVar6 = 0x16;
// ...
puVar3[uVar8] = bVar2 ^ bVar1;
puVar3[uVar8 + 1] = 0;
// ...
pbVar5 = local_e8[lVar10];
pbVar7 = local_198[lVar10];
uVar9 = (ulong) local_1b8[0];
lVar10 = lVar10 + 1;
} while (true);
}
Solo debemos tomar los valores de local_e8
y local_198
y aplicar XOR. Estos valores son constantes que se pueden ver en Ghidra, algunos de ellos son:
DAT_0012d0c9 XREF[3]: Java_com_example_anchored_MainAc
Java_com_example_anchored_MainAc
Java_com_example_anchored_MainAc
0012d0c9 74 undefined1 74h t
0012d0ca 00 ?? 00h
DAT_0012d0cb XREF[2]: Java_com_example_anchored_MainAc
FUN_0011af50:0011b171(*)
0012d0cb 75 ?? 75h u
0012d0cc 00 ?? 00h
DAT_0012d0cd XREF[1]: Java_com_example_anchored_MainAc
0012d0cd 39 ?? 39h 9
0012d0ce 00 ?? 00h
DAT_0012d0cf XREF[1]: Java_com_example_anchored_MainAc
0012d0cf 38 ?? 38h 8
0012d0d0 00 ?? 00h
DAT_0012d0d1 XREF[1]: Java_com_example_anchored_MainAc
0012d0d1 3f ?? 3Fh ?
0012d0d2 00 ?? 00h
Flag
Podemos usar Python para descifrarlo todo y encontrar la flag:
$ python3 -q
>>> from pwn import xor
>>>
>>> ct = b't%u9t8?M/~bx&uz-ebtux8'
>>> key = b'!K!K!KK~K!!KT!KKT!@!KK'
>>> xor(ct, key)
b'UnTrUst3d_C3rT1f1C4T3s'
Entonces, la flag es:
HTB{UnTrUst3d_C3rT1f1C4T3s}