Skip to content

Commit

Permalink
[Protector] Strong manifest protector with user dictionary based file…
Browse files Browse the repository at this point in the history
… confuser
  • Loading branch information
REAndroid committed Nov 20, 2024
1 parent a7beccf commit 4e7ce00
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 90 deletions.
5 changes: 4 additions & 1 deletion src/main/java/com/reandroid/apkeditor/protect/Confuser.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ public Set<String> getFilePaths() {
return filePaths;
}
public boolean isKeepType(String type) {
return getOptions().keepTypes.contains(type);
return getOptions().isKeepType(type);
}
public boolean isKeepAllTypes() {
return getOptions().isKeepAllTypes();
}
public Protector getProtector() {
return protector;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,19 @@
package com.reandroid.apkeditor.protect;

import com.reandroid.apk.ApkModule;
import com.reandroid.apk.DexFileInputSource;
import com.reandroid.apk.ResFile;
import com.reandroid.apk.UncompressedFiles;
import com.reandroid.apkeditor.utils.CyclicIterator;
import com.reandroid.archive.Archive;
import com.reandroid.utils.collection.CollectionUtil;

import java.util.List;

public class DirectoryConfuser extends Confuser {

private final CyclicIterator<String> namesIterator;

public DirectoryConfuser(Protector protector) {
super(protector, "DirectoryConfuser: ");
this.namesIterator = new CyclicIterator<>(loadDirNameList(protector.getApkModule()));
this.namesIterator = new CyclicIterator<>(
protector.getOptions().loadDirectoryNameDictionary());
}

@Override
Expand Down Expand Up @@ -88,41 +85,4 @@ private static String replaceDirectory(String path, String dirName) {
}
return dirName + simpleName;
}
private static String[] loadDirNameList(ApkModule apkModule) {
List<String> nameList = CollectionUtil.asList(
"AndroidManifest.xml",
"/AndroidManifest.xml",
"resources.arsc",
"/resources.arsc",
"classes.dex",
"/classes.dex",
"kotlin",
"META-INF",
"",
"kotlin/annotation",
"kotlin/collections",
"kotlin/coroutines",
"kotlin/internal",
"kotlin/ranges",
"kotlin/reflect",
"res/values/arrays.xml",
"res/values/attrs.xml",
"res/values/bools.xml",
"res/values/colors.xml",
"res/values/dimens.xml",
"res/values/drawables.xml",
"res/values/ids.xml",
"res/values/integers.xml",
"res/values/plurals.xml",
"res/values/public.xml",
"res/values/strings.xml",
"res/values/styles.xml"
);
List<DexFileInputSource> dexList = apkModule.listDexFiles();
int size = dexList.size();
for (int i = 1; i < size; i++) {
nameList.add(dexList.get(i).getAlias());
}
return nameList.toArray(new String[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class FileNameConfuser extends Confuser {

public FileNameConfuser(Protector protector) {
super(protector, "FileNameConfuser: ");
this.namesIterator = new CyclicIterator<>(loadFileNames());
this.namesIterator = new CyclicIterator<>(
protector.getOptions().loadFileNameDictionary());
}

@Override
Expand Down Expand Up @@ -94,21 +95,4 @@ private static String replaceSimpleName(String path, String symbol) {
}
return dirName + symbol + ext;
}
private static String[] loadFileNames() {
return new String[]{
".",
"//",
"///",
"////",
"\\\\",
"\\\\\\",
"\\/",
" ",
" ",
"classes.dex",
"AndroidManifest.xml",
"AndroidManifest",
"resources.arsc",
};
}
}
58 changes: 45 additions & 13 deletions src/main/java/com/reandroid/apkeditor/protect/ManifestConfuser.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
package com.reandroid.apkeditor.protect;

import com.reandroid.apk.ApkModule;
import com.reandroid.app.AndroidApiLevel;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.chunk.xml.ResXmlAttribute;
import com.reandroid.arsc.chunk.xml.ResXmlElement;
import com.reandroid.arsc.chunk.xml.UnknownResXmlNode;
import com.reandroid.arsc.chunk.ChunkType;
import com.reandroid.arsc.chunk.xml.*;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.IntegerItem;
import com.reandroid.arsc.item.ResXmlString;
import com.reandroid.arsc.pool.ResXmlStringPool;
import com.reandroid.utils.NumbersUtil;
import com.reandroid.utils.collection.CollectionUtil;

import java.io.IOException;
Expand Down Expand Up @@ -57,19 +58,50 @@ public void confuse() {
defaultAttributeSize, false);
manifestBlock.refresh();
}

private void placeBadChunk(AndroidManifestBlock manifestBlock) {
AndroidManifestBlock badManifest = new AndroidManifestBlock();
badManifest.setPackageName("android");
badManifest.setCompileSdk(AndroidApiLevel.U);
badManifest.setPlatformBuild(AndroidApiLevel.U);
badManifest.setVersionCode(1);
badManifest.setVersionName("1.0");
badManifest.refreshFull();
placeBadChunk(manifestBlock, ChunkType.XML_END_ELEMENT);
placeBadChunk(manifestBlock, ChunkType.XML_END_NAMESPACE);
}

private void placeBadChunk(AndroidManifestBlock manifestBlock, ChunkType chunkType) {
UnknownResXmlNode unknown = manifestBlock.newUnknown();
try {
unknown.readBytes(new BlockReader(badManifest.getBytes()));
unknown.readBytes(new BlockReader(
randomStringPool(chunkType)));
} catch (IOException ignored) {
}
manifestBlock.moveTo(unknown, 0);
}
private byte[] randomStringPool(ChunkType chunkType) {
ResXmlDocument document = new ResXmlDocument();
ResXmlStringPool stringPool = document.getStringPool();

Random random = new Random();

int size = NumbersUtil.min(20, 5 + random.nextInt(21));
stringPool.setUtf8(size % 2 == 0);

for (int i = 0; i < size; i++) {
String s = randomString();
ResXmlString xml = stringPool.getOrCreate(s);
xml.addReference(new IntegerItem());
}

stringPool.refresh();
stringPool.refresh();
stringPool.getHeaderBlock().setType(chunkType);

return stringPool.getBytes();
}
private String randomString() {
StringBuilder builder = new StringBuilder();
Random random = new Random();
int size = NumbersUtil.min(100, 15 + random.nextInt(90));
for (int i = 0; i < size; i++) {
char c = (char) (10 + random.nextInt(240));
builder.append(c);
}
return builder.toString();
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/reandroid/apkeditor/protect/Protector.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ public void runCommand() throws IOException {
new TableConfuser(this).confuse();
module.getTableBlock().refresh();
logMessage("Writing apk ...");
if (!options.skipZip) {
logMessage("Protecting zip structure");
if (options.confuse_zip) {
logMessage("Confusing zip structure ...");
new ProtectedFileWriter(module, options.outputFile).write();
} else {
module.writeApk(options.outputFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@
import com.reandroid.apkeditor.Options;
import com.reandroid.jcommand.annotations.CommandOptions;
import com.reandroid.jcommand.annotations.OptionArg;
import com.reandroid.utils.StringsUtil;
import com.reandroid.utils.collection.ArrayCollection;
import com.reandroid.utils.io.FileUtil;
import com.reandroid.utils.io.IOUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

Expand All @@ -35,12 +41,18 @@ public class ProtectorOptions extends Options {
@OptionArg(name = "-skip-manifest", flag = true, description = "protect_skip_manifest")
public boolean skipManifest;

@OptionArg(name = "-skip-zip", flag = true, description = "protect_skip_zip")
public boolean skipZip;
@OptionArg(name = "-confuse-zip", flag = true, description = "protect_confuse_zip")
public boolean confuse_zip;

@OptionArg(name = "-keep-type", description = "protect_keep_type")
public final Set<String> keepTypes = new HashSet<>();

@OptionArg(name = "-dic-dir-names", flag = true, description = "protect_dic_dir_name")
public File dic_dir_name;

@OptionArg(name = "-dic-file-names", flag = true, description = "protect_dic_file_name")
public File dic_file_name;

public ProtectorOptions() {
super();
}
Expand Down Expand Up @@ -77,4 +89,44 @@ public void validateOutput(boolean isFile) {
public File generateOutputFromInput(File input) {
return generateOutputFromInput(input, "_protected.apk");
}

public String[] loadDirectoryNameDictionary() {
return loadDictionary(dic_dir_name, "/protect_dic_dir_name.txt");
}
public String[] loadFileNameDictionary() {
return loadDictionary(dic_file_name, "/protect_dic_file_name.txt");
}

private String[] loadDictionary(File file, String resource) {
InputStream inputStream;
if (file != null) {
try {
inputStream = FileUtil.inputStream(file);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
} else {
inputStream = ProtectorOptions.class.getResourceAsStream(resource);
}
String full;
try {
full = IOUtil.readUtf8(inputStream);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
ArrayCollection<String> results = new ArrayCollection<>(
StringsUtil.split(full, '\n', true));
results.removeIf(StringsUtil::isEmpty);
return results.toArray(new String[results.size()]);
}
public boolean isKeepType(String type) {
Set<String> keepTypes = this.keepTypes;
return keepTypes.contains(type) ||
keepTypes.contains(KEEP_ALL_TYPES);
}
public boolean isKeepAllTypes() {
return keepTypes.contains(KEEP_ALL_TYPES);
}

private static final String KEEP_ALL_TYPES = "all-types";
}
43 changes: 32 additions & 11 deletions src/main/java/com/reandroid/apkeditor/protect/TableConfuser.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,49 @@ private void confuseWithUnknownChunk() {
tableBlock.refresh();
}
private void confuseTypeNames() {
//TODO: let the user choose which types to confuse,
// and use user provided type names
if (isKeepAllTypes()) {
logMessage("Skip type names");
return;
}
logMessage("Type names ...");
ApkModule apkModule = getApkModule();
TableBlock tableBlock = apkModule.getTableBlock();
for(PackageBlock packageBlock:tableBlock.listPackages()){
TypeStringPool pool = packageBlock.getTypeStringPool();
for(TypeString typeString : pool) {
String type = typeString.get();
if ("attr".equals(type) ) {
typeString.set("style");
} else if ("style".equals(type)) {
typeString.set("plurals");
} else if ("id".equals(type)) {
typeString.set("attr");
} else if ( "mipmap".equals(type)) {
typeString.set("id");
} else {
String replace = getReplacement(type);
if (type.equals(replace)) {
continue;
}
typeString.set(replace);
logVerbose("'" + type + "' -> '" + typeString.get() + "'");
}
}
tableBlock.refresh();
}

private String getReplacement(String type) {
if (isKeepType(type)) {
return type;
}

String replacement;

if ("attr".equals(type) ) {
replacement = "style";
} else if ("style".equals(type)) {
replacement = "plurals";
} else if ("id".equals(type)) {
replacement = "attr";
} else if ( "mipmap".equals(type)) {
replacement = "id";
} else {
replacement = type;
}
if (isKeepType(replacement)) {
replacement = type;
}
return replacement;
}
}
27 changes: 27 additions & 0 deletions src/main/resources/protect_dic_dir_name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
AndroidManifest.xml
/AndroidManifest.xml
resources.arsc
/resources.arsc
classes.dex
/classes.dex
kotlin
META-INF

kotlin/annotation
kotlin/collections
kotlin/coroutines
kotlin/internal
kotlin/ranges
kotlin/reflect
res/values/arrays.xml
res/values/attrs.xml
res/values/bools.xml
res/values/colors.xml
res/values/dimens.xml
res/values/drawables.xml
res/values/ids.xml
res/values/integers.xml
res/values/plurals.xml
res/values/public.xml
res/values/strings.xml
res/values/styles.xml"
13 changes: 13 additions & 0 deletions src/main/resources/protect_dic_file_name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.
//
///
////
\\\\
\\\\\\
\\/


classes.dex
AndroidManifest.xml
AndroidManifest
resources.arsc
Loading

0 comments on commit 4e7ce00

Please sign in to comment.