Open pr3wtd opened 6 years ago
Hi @pr3wtd,
is this the sample we talked yesterday about? Do you have another samples which are 100% sure obfuscated with Allatori?
Thanks for opening an issue mate.
Also, can you upload the sample in this post or at Koodoous?
Cheers
Hi @enovella,
Correct, that's the one. I couldn't attach the file in this post, it can be downloaded from: https://www74.zippyshare.com/v/cg3GOrqh/file.html (password: infected)
I've seen a couple of APKs with this kind of obfuscation. I will let you know as soon as I come accross some of them.
Hi @pr3wtd,
I cannot download the file, could you attach it here with extension zip
?
Best
Hi @pr3wtd,
Can you provide more samples? It seems we can target the 12-char long classes, methods and fields all of them built with the regex [iI]{12}
. I do need more samples to tweak the rule.
package nul;
import java.util.zip.*;
import com.google.gson.*;
public final class iIiiiIiiIiiI implements IIIIIiiIIIiI
{
private static final /* synthetic */ byte[] IiiiiiiIiIII;
public static final iiiiIIiIIIIi iIiIiiiIIIii;
private /* synthetic */ iIiIIIiIIIiI IiIIiiiiIIiI;
private /* synthetic */ byte[] IIiiIiiIIiII;
private /* synthetic */ iIiIIIiIIIiI IIIIIiiiiiii;
private /* synthetic */ IIIIiIiiiiiI IiiIIiiIiIiI;
private /* synthetic */ iIiIIIiIIIiI IIiIiiiiIiII;
public iiiiIIiIIIIi do() {
int n;
iIiiiIiiIiiI iIiiiIiiIiiI;
if (this.IIIIIiiiiiii != null) {
n = 8;
iIiiiIiiIiiI = this;
}
else {
n = 0;
iIiiiIiiIiiI = this;
}
return new iiiiIIiIIIIi(n + ((iIiiiIiiIiiI.IIiIiiiiIiII != null) ? 8 : 0) + ((this.IiIIiiiiIIiI != null) ? 8 : 0) + ((this.IiiIIiiIiIiI != null) ? 4 : 0));
}
public iiiiIIiIIIIi new() {
return new iiiiIIiIIIIi((this.IIIIIiiiiiii != null) ? 16 : 0);
}
public void new(final byte[] this, int this, int this) throws ZipException {
if (this == 0) {
return;
}
if (this < 16) {
throw new ZipException("Zip64 extended information must contain both size values in the local file header.");
}
this.IIIIIiiiiiii = new iIiIIIiIIIiI(this, this);
this += 8;
this.IIiIiiiiIiII = new iIiIIIiIIIiI(this, this);
final int n = this - 16;
this += 8;
if ((this = n) >= 8) {
final int n2 = this;
this += 8;
this.IiIIiiiiIIiI = new iIiIIIiIIIiI(this, n2);
this -= 8;
}
if (this >= 4) {
final int n3 = this;
this += 4;
this.IiiIIiiIiIiI = new IIIIiIiiiiiI(this, n3);
this -= 4;
}
}
public iIiIIIiIIIiI do() {
return this.IiIIiiiiIIiI;
}
public void do(final iIiIIIiIIIiI this) {
this.IiIIiiiiIIiI = this;
}
public IIIIiIiiiiiI new() {
return this.IiiIIiiIiIiI;
}
public byte[] new() {
final byte[] array;
int new1 = this.new(array = new byte[this.do().new()]);
if (this.IiIIiiiiIIiI != null) {
final byte[] new2 = this.IiIIiiiiIIiI.new();
final int n = 0;
final byte[] array2 = array;
final int n2 = new1;
new1 += 8;
System.arraycopy(new2, n, array2, n2, 8);
}
if (this.IiiIIiiIiIiI != null) {
final byte[] new3 = this.IiiIIiiIiIiI.new();
final int n3 = 0;
final byte[] array3 = array;
final int n4 = new1;
new1 += 4;
System.arraycopy(new3, n3, array3, n4, 4);
}
return array;
}
public byte[] enum() {
if (this.IIIIIiiiiiii == null && this.IIiIiiiiIiII == null) {
return iIiiiIiiIiiI.IiiiiiiIiIII;
}
if (this.IIIIIiiiiiii == null || this.IIiIiiiiIiII == null) {
throw new IllegalArgumentException("Zip64 extended information must contain both size values in the local file header.");
}
final byte[] this2 = new byte[16];
this.new(this2);
return this2;
}
public void enum(final iIiIIIiIIIiI this) {
this.IIiIiiiiIiII = this;
}
public void new(final IIIIiIiiiiiI this) {
this.IiiIIiiIiIiI = this;
}
public void new(final boolean this, final boolean this, final boolean this, final boolean this) throws ZipException {
if (this.IIiiIiiIIiII != null) {
int n;
boolean b;
if (this) {
n = 8;
b = this;
}
else {
n = 0;
b = this;
}
final int n2 = n + (b ? 8 : 0) + (this ? 8 : 0) + (this ? 4 : 0);
if (this.IIiiIiiIIiII.length < n2) {
throw new ZipException(new StringBuilder().insert(0, GsonBuilder.new("\u000e]\u0003L\u001fY\u0001\u0018\tQ\u001f]\u000eL\u0002J\u0014\u0018\u0017Q\u001d\u000eY\u0018\b@\u0019]\u0003\\\b\\MQ\u0003^\u0002J\u0000Y\u0019Q\u0002VM]\u0015L\u001fYM^\u0004]\u0001\\JKMT\bV\nL\u0005\u0018\tW\bK\u0003\u001f\u0019\u0018\u0000Y\u0019[\u0005\u0018\u000e]\u0003L\u001fY\u0001\u0018\tQ\u001f]\u000eL\u0002J\u0014\u0018\tY\u0019YC\u0018M}\u0015H\b[\u0019]\t\u0018\u0001]\u0003_\u0019PM")).append(n2).append(IiIIiIiIiiii.new("\u001fKJ]\u001f@L\t")).append(this.IIiiIiiIIiII.length).toString());
}
int n3 = 0;
if (this) {
final byte[] iIiiIiiIIiII = this.IIiiIiiIIiII;
final int n4 = n3;
n3 += 8;
this.IIIIIiiiiiii = new iIiIIIiIIIiI(iIiiIiiIIiII, n4);
}
if (this) {
final byte[] iIiiIiiIIiII2 = this.IIiiIiiIIiII;
final int n5 = n3;
n3 += 8;
this.IIiIiiiiIiII = new iIiIIIiIIIiI(iIiiIiiIIiII2, n5);
}
if (this) {
final byte[] iIiiIiiIIiII3 = this.IIiiIiiIIiII;
final int n6 = n3;
n3 += 8;
this.IiIIiiiiIIiI = new iIiIIIiIIIiI(iIiiIiiIIiII3, n6);
}
if (this) {
final byte[] iIiiIiiIIiII4 = this.IIiiIiiIIiII;
final int n7 = n3;
n3 += 4;
this.IiiIIiiIiIiI = new IIIIiIiiiiiI(iIiiIiiIIiII4, n7);
}
}
}
public iIiiiIiiIiiI(final iIiIIIiIIIiI this, final iIiIIIiIIIiI this) {
final IIIIiIiiiiiI iiiIiIiiiiiI = null;
this(this, this, (iIiIIIiIIIiI)iiiIiIiiiiiI, iiiIiIiiiiiI);
}
public iiiiIIiIIIIi enum() {
return iIiiiIiiIiiI.iIiIiiiIIIii;
}
public iIiIIIiIIIiI enum() {
return this.IIIIIiiiiiii;
}
public iIiIIIiIIIiI new() {
return this.IIiIiiiiIiII;
}
public void new(final iIiIIIiIIIiI this) {
this.IIIIIiiiiiii = this;
}
static {
iIiIiiiIIIii = new iiiiIIiIIIIi(1);
IiiiiiiIiIII = new byte[0];
}
public void enum(final byte[] this, int this, final int this) throws ZipException {
final int n = this;
this.IIiiIiiIIiII = new byte[this];
System.arraycopy(this, n, this.IIiiIiiIIiII, 0, this);
if (this >= 28) {
this.new(this, this, this);
return;
}
if (this == 24) {
this.IIIIIiiiiiii = new iIiIIIiIIIiI(this, this);
this += 8;
this.IIiIiiiiIiII = new iIiIIIiIIIiI(this, this);
this += 8;
this.IiIIiiiiIIiI = new iIiIIIiIIIiI(this, this);
return;
}
if (this % 8 == 4) {
this.IiiIIiiIiIiI = new IIIIiIiiiiiI(this, this + this - 4);
}
}
public iIiiiIiiIiiI() {
}
private /* synthetic */ int new(final byte[] this) {
int n = 0;
if (this.IIIIIiiiiiii != null) {
final byte[] new1 = this.IIIIIiiiiiii.new();
final int n2 = 0;
n += 8;
System.arraycopy(new1, n2, this, n2, 8);
}
if (this.IIiIiiiiIiII != null) {
final byte[] new2 = this.IIiIiiiiIiII.new();
final int n3 = 0;
final int n4 = n;
n += 8;
System.arraycopy(new2, n3, this, n4, 8);
}
return n;
}
public iIiiiIiiIiiI(final iIiIIIiIIIiI this, final iIiIIIiIIIiI this, final iIiIIIiIIIiI this, final IIIIiIiiiiiI this) {
this.IIIIIiiiiiii = this;
this.IIiIiiiiIiII = this;
this.IiIIiiiiIIiI = this;
this.IiiIIiiIiIiI = this;
}
}
Hey @enovella,
These samples look similarily obfuscated: 4991017aabfbeea83556691a2d23183d 9dbcbe543e7cbaaf751e7eca3dd60bb9
Hi @pr3wtd,
I cannot find this sample 4991017aabfbeea83556691a2d23183d
. Can you upload it here? Please dont use external file sharing websites. Just Github servers.
Also, Koodous is very unstable these days, and very often the access to APKs is somehow limited.
Here you go. 4991017aabfbeea83556691a2d23183d.zip
It seems that this sample 9dbcbe543e7cbaaf751e7eca3dd60bb9
is also not available at Koodous.
Just opened the one you shared over here. Some questions:
Lcom/android
seems to be suspicious. Wondering if this obfuscation is customized for malware only or it is a real obfuscator, which has been used for malware purposes.Heavy use of reflection
No answers? Otherwise we will close the issue.
Sorry, currenly I have totally no time to pursue this issue. I will move it on in the future and put a reference on this particular issue.
I'm going to keep this open since:
It's unfortunate we don't know more details about what exactly it is, and it would be nice to have those details, but we could still always create a rule to detect this, call it whatever, and link to this issue in the rules if people have more info.
I also like having these types of issues because they're an easy way for someone to contribute to the project.
Found an APK with this: (already obfuscated with DexProtector
)
package com.microblink.secured;
import com.microblink.util.Log;
public class IIlIIlIIII {
private static Error llIIlIlIIl;
private static boolean llIIlllIll;
static {
}
public IIlIIlIIII() {
super();
}
public static void IlIIlIIIII() {
if(!IIlIIlIIII.IlIIlllIIl()) {
Error v0 = IIlIIlIIII.llIIlIlIIl;
if(v0 != null) {
throw v0;
}
throw new RuntimeException("Native library is not loaded");
}
}
public static boolean IlIIlllIIl() {
if(!IIlIIlIIII.llIIlllIll) {
try {
if(lIlIIIIlIl.llIIlIIlll()) {
String[] v1_1 = llIIlIlIIl.llIIlIlIIl;
int v2 = v1_1.length;
int v3;
for(v3 = 0; v3 < v2; ++v3) {
String v5 = v1_1[v3];
Log.d(IIlIIlIIII.class, "Loading lib{}.so", new Object[]{v5});
System.loadLibrary(v5);
}
IIlIIlIIII.llIIlllIll = true;
goto label_32;
}
throw new UnsatisfiedLinkError("Incompatible CPU!");
}
catch(Error v1) {
IIlIIlIIII.llIIlllIll = false;
Log.e(IIlIIlIIII.class, ((Throwable)v1), "error loading native library", new Object[0]);
IIlIIlIIII.llIIlIlIIl = v1;
}
}
label_32:
return IIlIIlIIII.llIIlllIll;
}
public static Error llIIlIlIIl() {
return IIlIIlIIII.llIIlIlIIl;
}
public static boolean llIllIIlll() {
return IIlIIlIIII.llIIlllIll;
}
}
sample: b2f94d7521326fbf01e9749acc3033b3881d97457249eaadd48567a26d11087a
Hi, suprised to see that apkid can't detect allatori. Here is what I know so far about allatori.
There are 2 version of it. Demo and commercial (?) one.
Demo version only obfuscates strings ( uses mostly static class name : ALLATORIxDEMO
for decryption )
In demo version strings are obfuscated with xor loops that have 2 key. No stack trace is used. There are multiple xor functions.
Commercial version use IiIIIIIIii
like strings as a class name and make use of stack trace in obfuscation.
Sample
APKiD can detect Allatori demo version:
> apkid allatoridemo_d810d0059234f4fb2fe4084a3021c2bf94e986011bb2299d1e132e6b3b46e486.apk
[+] APKiD 2.1.0 :: from RedNaga :: rednaga.io
[*] allatoridemo_d810d0059234f4fb2fe4084a3021c2bf94e986011bb2299d1e132e6b3b46e486.apk!classes.dex
|-> anti_debug : Debug.isDebuggerConnected() check
|-> anti_vm : Build.MANUFACTURER check, Build.MODEL check, Build.PRODUCT check, Build.TAGS check
|-> compiler : dx
|-> obfuscator : Allatori demo
The commercial version of Allatori has not been implemented yet because the lack of certainty and verified samples.
> apkid allatori_c0dd3437035073e3ef0c421c5410b874f6940c8cd7c0d829fb3297c63fa70216.apk
[+] APKiD 2.1.0 :: from RedNaga :: rednaga.io
[*] allatori_c0dd3437035073e3ef0c421c5410b874f6940c8cd7c0d829fb3297c63fa70216.apk!classes.dex
|-> anti_debug : Debug.isDebuggerConnected() check
|-> anti_vm : Build.MANUFACTURER check, Build.MODEL check, Build.PRODUCT check, Build.TAGS check, SIM operator check, network operator name check, possible ro.secure check
|-> compiler : dx
Are you willing to pull-request it?
Using only the string IiIIIIIIii
may lead to false positives. Could you please elaborate a bit more about how to create a solid rule for detecting the commercial version of Allatori?
It seems that this sample
9dbcbe543e7cbaaf751e7eca3dd60bb9
is also not available at Koodous.Just opened the one you shared over here. Some questions:
- How sure are you that this is Allatori? If so, why?
- Is this malware right? Is Allatori heavily using reflection? or this is part of the malware?
- The package name of
Lcom/android
seems to be suspicious. Wondering if this obfuscation is customized for malware only or it is a real obfuscator, which has been used for malware purposes.- Can you provide ideas on how to fingerprint it?
- Anything unique of these obfuscator/malware samples?
Heavy use of reflection
@eybisi could you also verify this sample? Are you sure that's protected with Allatori?
Found an APK with this: (already obfuscated with
DexProtector
)package com.microblink.secured; import com.microblink.util.Log; public class IIlIIlIIII { private static Error llIIlIlIIl; private static boolean llIIlllIll; static { } public IIlIIlIIII() { super(); } public static void IlIIlIIIII() { if(!IIlIIlIIII.IlIIlllIIl()) { Error v0 = IIlIIlIIII.llIIlIlIIl; if(v0 != null) { throw v0; } throw new RuntimeException("Native library is not loaded"); } } public static boolean IlIIlllIIl() { if(!IIlIIlIIII.llIIlllIll) { try { if(lIlIIIIlIl.llIIlIIlll()) { String[] v1_1 = llIIlIlIIl.llIIlIlIIl; int v2 = v1_1.length; int v3; for(v3 = 0; v3 < v2; ++v3) { String v5 = v1_1[v3]; Log.d(IIlIIlIIII.class, "Loading lib{}.so", new Object[]{v5}); System.loadLibrary(v5); } IIlIIlIIII.llIIlllIll = true; goto label_32; } throw new UnsatisfiedLinkError("Incompatible CPU!"); } catch(Error v1) { IIlIIlIIII.llIIlllIll = false; Log.e(IIlIIlIIII.class, ((Throwable)v1), "error loading native library", new Object[0]); IIlIIlIIII.llIIlIlIIl = v1; } } label_32: return IIlIIlIIII.llIIlllIll; } public static Error llIIlIlIIl() { return IIlIIlIIII.llIIlIlIIl; } public static boolean llIllIIlll() { return IIlIIlIIII.llIIlllIll; } }
sample:
b2f94d7521326fbf01e9749acc3033b3881d97457249eaadd48567a26d11087a
@eybisi could you also verify this sample? Are you sure that's protected with Allatori?
Too many false positives!
rule allatori : obfuscator
{
meta:
description = "Allatori"
url = "http://www.allatori.com/features.html"
author = "Eduardo Novella"
sample = "c0dd3437035073e3ef0c421c5410b874f6940c8cd7c0d829fb3297c63fa70216"
strings:
// null-prev-str + len + str + null
$method_7 = { 00 07 (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) 00}
$method_8 = { 00 08 (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) 00}
$method_10 = { 00 0A (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) (49|69|6a|6c|4a|4c) 00}
// $method = /[iIlLjJ]{7,12}/
condition:
(#method_7 > 10 or #method_8 > 10 or #method_10 > 10) and is_dex
// #method > 10 and is_dex and not allatori_demo
}
I don't think these are allatori. For string encryption allatori use basic xor or make use of stack trace. I didn't see any reflection+string encryption in malware samples for allatori.
Also I saw demo samples without ALLATORIxDEMO
string instead random function names. Couldn't find them right now, but I'm pretty sure. Because I used this script to decrypt strings and I was changing function name day by day.
If we use string encryption function as base point for detection, I propose following dalvik opcodes for fingerprinting. At least we can identify allatori samples with string encryption is on.
rule allatori_demo
{
strings:
/*
char[] cArr = new char[length];
int i = length - 1;
while (i >= 0) {
int i2 = i - 1;
cArr[i] = (char) (str.charAt(i) ^ 'T');
if (i2 < 0) {
break;
}
i = i2 - 1;
cArr[i2] = (char) (str.charAt(i2) ^ '9');
}
*/
$twokeyxor = {
0a 00 // move-result v0
23 01 ?? ?? // new-array v1, v0, [C
d8 00 00 ff // add-int/lit8 v0, v0, -0x1
3a 00 1b 00 // if-ltz v0, :cond_0
6e 20 ?? ?? 04 00 // invoke-virtual {p0, v0}, Ljava/lang/String;->charAt(I)C
0a 02 // move-result v2
d8 03 00 ff // add-int/lit8 v3, v0, -0x1
df 02 02 ?? // xor-int/lit8 v2, v2, 0x54
8e 22 // int-to-char v2, v2
50 02 01 00 // aput-char v2, v1, v0
3a 03 0e 00 // if-ltz v3, :cond_0
d8 00 03 ff // add-int/lit8 v0, v3, -0x1
6e 20 ?? ?? 34 00 // invoke-virtual {p0, v3}, Ljava/lang/String;->charAt(I)C
0a 02 // move-result v2
df 02 02 ?? // xor-int/lit8 v2, v2, 0x39
8e 22 // int-to-char v2, v2
50 02 01 03 // aput-char v2, v1, v3
28 e6 // goto
}
condition:
$is_dex and $twokeyxor
}
Same idea for commercial rule.
rule allatori_commercial
{
strings:
/*
while (i >= 0) {
int i4 = i3 - 1;
cArr[i3] = (char) ((str.charAt(i3) ^ stringBuffer.charAt(i2)) ^ 11);
if (i4 < 0) {
break;
}
i3 = i4 - 1;
int i5 = i2 - 1;
cArr[i4] = (char) ((str.charAt(i4) ^ stringBuffer.charAt(i2)) ^ 'N');
if (i5 < 0) {
i5 = length;
}
i2 = i5;
i = i3;
}
return new String(cArr);
*/
$stacktracexor = {
0a 00 // move-result v0
23 05 ?? ?? // new-array v5, v0, [C
d8 00 00 ff // add-int/lit8 v0, v0, -0x1
01 13 // move v3, v1
01 02 // move v2, v0
3b 00 08 00 // if-gez v0, :cond_1
22 00 ?? ?? // new-instance v0, Ljava/lang/String;
70 20 ?? ?? 50 00 // invoke-direct {v0, v5}, Ljava/lang/String;-><init>([C)V
11 00 // return-object v0
d8 06 02 ff // add-int/lit8 v6, v2, -0x1
6e 20 ?? ?? 28 00 // invoke-virtual {p0, v2}, Ljava/lang/String;->charAt(I)C
0a 00 // move-result v0
6e 20 ?? ?? 34 00 // invoke-virtual {v4, v3}, Ljava/lang/String;->charAt(I)C
0a 07 // move-result v7
b7 70 // xor-int/2addr v0, v7
df 00 00 ?? // xor-int/lit8 v0, v0, 0x35
8e 00 // int-to-char v0, v0
50 00 05 02 // aput-char v0, v5, v2
3a 06 ea ff // if-ltz v6,
6e 20 ?? ?? 68 00 // invoke-virtual {p0, v6}, Ljava/lang/String;->charAt(I)C
0a 00 // move-result v0
6e 20 ?? ?? 34 00 // invoke-virtual {v4, v3}, Ljava/lang/String;->charAt(I)C
0a 02 // move-result v2
b7 20 // xor-int/2addr v0, v2
df 00 00 ?? // xor-int/lit8 v0, v0, 0x6
8e 07 // int-to-char v7, v0
d8 02 06 ff // add-int/lit8 v2, v6, -0x1
d8 00 03 ff // add-int/lit8 v0, v3, -0x1
50 07 05 06 // aput-char v7, v5, v6
3b 00 03 00 // if-gez v0,
01 10 // move v0, v1
}
condition:
$is_dex and $stacktracexor
}
Have you run these two rules against a bunch apks to detect false positives? The ideal rule should contain something unique to this protector. Open a PR, and I will test it.
Allatori obfuscation including custom class renaming
Sample file: f9f659da6cad4edbef46e452c0e2dfce0f124fd64662e22c30be70daaef1c44d
Reference: https://www.virustotal.com/#/file/f9f659da6cad4edbef46e452c0e2dfce0f124fd64662e22c30be70daaef1c44d/community