很重要的参考博客:http://blog.csdn.net/jiangwei0910410003/article/details/50568487
在此感谢原作者
本DEMO目标为隐藏APP启动图标。
方法为修改AXML文件中<category android:name="android.intent.category.LAUNCHER"/>至<category android:name="android.intent.category.DEFAULT"/>同时修改各项参数:
文件大小段,字符串数目,字符串偏移量,字符串chunk参数,字符串段开始位置偏移量。
注意:
修改AXML文件后要重新签名
最后的文件一定要4位对齐不然会导致APP无法安装
代码如下:
package cn.com.silence;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
public class MainClass {
/**
* 用来保存所有的字符串,在xmlcontent里会调用
*/
public static ArrayList<String> stringList = new ArrayList<String>();
/**
* 存储所有的resouceID暂时没用上
*/
private static ArrayList<Integer> resIdList = new ArrayList<Integer>();
/**
* android.intent.category数据段
*/
private static final byte[] CATEGOTY = new byte[] { 0x20, 0x00, 0x61, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x69, 0x00, 0x64,
0x00, 0x2E, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6E, 0x00, 0x74, 0x00, 0x2E, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74,
0x00, 0x65, 0x00, 0x67, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x79, 0x00,
0x2E, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x55, 0x00, 0x4e, 0x00, 0x43,
0x00, 0x48, 0x00, 0x45, 0x00, 0x52, 0x00, 0x00, 0x00 };
/**
* android.intent.DEFAULT数据段后面的0是用来4位对齐,文件不是4的整数倍无法安装
*/
private static final byte[] DEFAULT = new byte[] { 0x1F, 0x00, 0x61, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x69, 0x00, 0x64,
0x00, 0x2E, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6E, 0x00, 0x74, 0x00, 0x2E, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74,
0x00, 0x65, 0x00, 0x67, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x79, 0x00,
0x2E, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x41, 0x00, 0x55,
0x00, 0x4c, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00 };
/**
* categroy数据段长度
*/
private static final int CATEGORYLENGTH = CATEGOTY.length;
/**
* category string字段位置
*/
private static int categoryPosition = -1;
/**
* string段结束位置
*/
private static int stringEndPosition;
/**
* 偏移量段结束位置
*/
private static int stringStartPosition;
/**
* category后string段偏移量
*/
// private static ArrayList<HashMap<String, Integer>> laterStringOffset;
private static int xmlSize;
private static byte[] xmlData;
/**
* 修改xmlcontent段中,category块里参数对应value的指向stringlist的值,即从launcher重新指向default
*
* @param pos tag中value所在的位置
*/
private static void modifyContent(int pos) {
byte[] buffer = Utils.intToByte(stringList.size());
System.arraycopy(buffer, 0, xmlData, pos, 4);
System.out.println("Content modify finished...");
}
/**
* 修改文件大小
*/
private static void modifyFileSize() {
System.out.println(xmlSize);
byte[] sizeData = Utils.intToByte(xmlSize);
System.arraycopy(sizeData, 0, xmlData, 4, 4);
System.out.println("file size modified...");
}
/**
* 修改字符串端开始前的参数
*/
private static void modifyOffset() {
byte[] stringCount = new byte[4];
System.arraycopy(xmlData, 16, stringCount, 0, 4);
int lastStringOffset = 36 + Utils.byteToInt(stringCount) * 4;
stringCount = Utils.intToByte(Utils.byteToInt(stringCount) + 1);
System.arraycopy(stringCount, 0, xmlData, 16, 4);// string count + 1
byte[] newStringChunkSize = new byte[4];
System.arraycopy(xmlData, 12, newStringChunkSize, 0, 4);
newStringChunkSize = Utils.intToByte(Utils
.byteToInt(newStringChunkSize) + DEFAULT.length + 4);
System.arraycopy(newStringChunkSize, 0, xmlData, 12, 4);// stringChunkSize
// + 72
int z = stringStartPosition - 8 + 4;
byte[] newStringStartOffset = Utils.intToByte(z);
System.arraycopy(newStringStartOffset, 0, xmlData, 28, 4);// string
// start
// pos++
byte[] temp = new byte[xmlSize + 4];
byte[] nStringOffset = Utils.intToByte(stringEndPosition
- stringStartPosition - DEFAULT.length);// add new offset
System.arraycopy(xmlData, 0, temp, 0, lastStringOffset);
System.arraycopy(nStringOffset, 0, temp, lastStringOffset, 4);
System.arraycopy(xmlData, lastStringOffset, temp, lastStringOffset + 4,
xmlSize - lastStringOffset);
xmlSize = xmlSize + 4;
xmlData = new byte[xmlSize];
System.arraycopy(temp, 0, xmlData, 0, xmlSize);
stringEndPosition += 4;
System.out.println("offset modify finished...");
}
/**
* 增加android.intent.category.default字符串
*/
private static void modifyString() {
byte[] temp = new byte[xmlSize + DEFAULT.length];
System.arraycopy(xmlData, 0, temp, 0, stringEndPosition);
System.arraycopy(DEFAULT, 0, temp, stringEndPosition, DEFAULT.length);
System.arraycopy(xmlData, stringEndPosition, temp, stringEndPosition
+ DEFAULT.length, xmlSize - stringEndPosition);
stringEndPosition += DEFAULT.length;
xmlSize = xmlSize + DEFAULT.length;
xmlData = new byte[xmlSize];
System.arraycopy(temp, 0, xmlData, 0, xmlSize);
}
/**
* 获取string段结束的位置
* @param data 二进制axml文件
* @return 位置
*/
private static int getEndPosition(byte[] data) {
int i = 0;
byte[] temp = new byte[4];
System.arraycopy(data, 12, temp, 0, 4);
i = Utils.byteToInt(temp);
i = i + 8;
return i;
}
/**
* 一大堆里最后用上的只有string偏移量段结束的位置——字符串段开始的位置
* @param data AXML二进制文件
*/
private static void getCategory(byte[] data) {
// int i = -1;
byte[] tempNum = new byte[4];
System.arraycopy(data, 28, tempNum, 0, 4);
stringStartPosition = Utils.byteToInt(tempNum) + 8;// 偏移量段结束位置
System.out.println("string off set position: " + stringStartPosition);
byte[] temp = new byte[CATEGORYLENGTH];
for (int j = 36; j < stringStartPosition; j += 4) {
System.arraycopy(data, j, tempNum, 0, 4);
int stringOffset = Utils.byteToInt(tempNum) + stringStartPosition;
// System.out.println(stringOffset);
System.arraycopy(data, stringOffset, temp, 0, CATEGORYLENGTH);
if (Arrays.equals(temp, CATEGOTY)) {
categoryPosition = stringOffset;
break;
}
}
}
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream(new File(
"E:/AndroidManifest.xml"));
xmlSize = fis.available();
xmlData = new byte[xmlSize];
fis.read(xmlData, 0, xmlSize);
fis.close();
stringEndPosition = getEndPosition(xmlData);
System.out.println("StringChunk end position: " + stringEndPosition);
getCategory(xmlData);// category位置
if (categoryPosition == -1) {
System.out.println("Category not found");
Runtime.getRuntime().exit(0);
}
System.out.println("category string position: " + categoryPosition);
System.out.println("string start pos: " + stringStartPosition);
System.out.println("string end pos: " + stringEndPosition);
stringList = ReadData.strings(xmlData, stringStartPosition);
resIdList = ReadData.resID(xmlData, stringEndPosition);
// int count = 0;
int pos = 0;
modifyString();
modifyOffset();
Find find = new Find(xmlData, stringEndPosition);
pos = find.getPosition();
byte[] buffer = new byte[4];
System.arraycopy(xmlData, pos + 44, buffer, 0, 4);
System.out.println("valueString = "
+ stringList.get(Utils.byteToInt(buffer)));
modifyContent(pos + 44);
modifyFileSize();
FileOutputStream fos = new FileOutputStream(new File(
"E:/AndroidManifest.xml"));
fos.write(xmlData, 0, xmlSize);
fos.flush();
fos.close();
System.out.println("All finished....");
}
}
下面这个类用于读取string和resourceID
package cn.com.silence;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
public class ReadData {
public static ArrayList<String> strings(byte[] data, int stringOffsetPosition){
ArrayList<String> stringList = new ArrayList<String>();
byte[] tempNum = new byte[4];
byte[] string;
System.out.println("********************Strings below***********************");
for (int j = 36; j < stringOffsetPosition; j += 4) {
System.arraycopy(data, j, tempNum, 0, 4);
int stringOffset = Utils.byteToInt(tempNum) + stringOffsetPosition;
tempNum = new byte[]{0, 0, 0, 0};
System.arraycopy(data, stringOffset, tempNum, 0, 2);
int stringLength = Utils.byteToInt(tempNum);
string = new byte[stringLength * 2];
System.arraycopy(data, stringOffset + 2, string, 0, string.length);
byte[] printUse = new byte[stringLength];
for (int i = 0; i < stringLength; i++) {
System.arraycopy(string, i * 2, printUse, i, 1);
}
System.out.println(new String(printUse));
stringList.add(new String(printUse));
}
System.out.println("********************Strings finished***********************");
return stringList;
}
public static ArrayList<Integer> resID(byte[] data, int startPos){
byte[] sizeData = new byte[4];
byte[] idData = new byte[4];
System.arraycopy(data, startPos + 4, sizeData, 0, 4);
int size = Utils.byteToInt(sizeData);
ArrayList<Integer> resourceIdList = new ArrayList<Integer>(size - 2);
System.out.println("********************resID below***********************");
System.arraycopy(data, startPos, idData, 0, 4);
System.out.println("start data: " + Arrays.toString(idData) + "\nsize: " + size);
for (int i = 0; i < (size - 8); i += 4) {
System.arraycopy(data, startPos + 8 + i, idData, 0, 4);
int resID = Utils.byteToInt(idData);
System.out.println("id: "+ resID + ", hex: " + Arrays.toString(idData));
resourceIdList.add(resID);
}
System.out.println("********************resID finished***********************");
return resourceIdList;
}
}
package cn.com.silence;
public class Utils {
public static int byteToInt(byte[] buffer) {
int i = (((buffer[3] << 24) & 0xff000000)
| ((buffer[2] << 16) & 0xff0000) | ((buffer[1] << 8) & 0xff00) | ((buffer[0]) & 0xff));
return i;
}
public static byte[] intToByte(int i) {
byte[] buffer = new byte[4];
buffer[3] = (byte) (((i) >> 24) & 0xff);
buffer[2] = (byte) (((i) >> 16) & 0xff);
buffer[1] = (byte) (((i) >> 8) & 0xff);
buffer[0] = (byte) ((i) & 0xff);
return buffer;
}
}
下面这个类用于查找<category android:name="android.intent.category.LAUNCHER"/>字段在xmlcontent的位置,并返回
package cn.com.silence;
import java.util.Arrays;
public class Find {
private int position = 0;
private int output = -1;
private byte[] data;
private final byte[] tagBegin = { 0x02, 0x01, 0x10, 0x00 };
private final byte[] tagEnd = { 0x03, 0x01, 0x10, 0x00 };
public Find(byte[] b, int pos) {
data = b;
int count = 0;
for (position = pos; position + 5 < data.length; position++) {
if (tagStart()) {
count++;
if (count == 5 && check()) {
output = position;
System.out.println("xmlContent Category Position: "
+ position);
break;
}
} else if (tagEnd()) {
count--;
}
}
}
private boolean check() {
byte[] buffer = new byte[4];
System.arraycopy(data, position + 20, buffer, 0, 4);
byte[] buffer1 = new byte[4];
System.arraycopy(data, position + 44, buffer1, 0, 4);
System.out.println(position);
System.out.println(Utils.byteToInt(buffer) + "###############################"
+ MainClass.stringList.get(Utils.byteToInt(buffer)).equals(
"category"));
System.out.println(Utils.byteToInt(buffer1) + "###############################"
+ MainClass.stringList.get(Utils.byteToInt(buffer1)).equals(
"android.intent.category.LAUNCHER"));
if (MainClass.stringList.get(Utils.byteToInt(buffer))
.equals("category")
&& MainClass.stringList.get(Utils.byteToInt(buffer1)).equals(
"android.intent.category.LAUNCHER")) {
return true;
} else {
return false;
}
}
private boolean tagStart() {
byte[] a = new byte[4];
System.arraycopy(data, position, a, 0, 4);
if (Arrays.equals(a, tagBegin)) {
return true;
} else {
return false;
}
}
private boolean tagEnd() {
byte[] a = new byte[4];
System.arraycopy(data, position, a, 0, 4);
if (Arrays.equals(a, tagEnd)) {
return true;
} else {
return false;
}
}
public int getPosition() {
return output;
}
}