一、前言
(本来已经放弃了,感谢kanxue、作者卓桐、小编的鼓励。使得能够在快结束时提交答案)
本题使用了一些android加固技术:包括
1)多种反调试
2)ollvm混淆
3)native函数多次动态注册
4) 简单的dex vmp虚拟机(advmp)
5) java层字符混淆
6) java部分函数插花指令
7)so 字符串加密(应该是ollvm实现的)
8)java字符串加密
4、将VMP大部分指令识别后,还原出加解密代码,发现输入竟然是随机的,因为其java层将输入的flag通过一些查表变换后使用了base64进行加密,但是这个base64加密的基准字符,使用了一个random随机生成的。当再次加密时这个基准又重新随机生成,所以2次输入相同的sn,base64的结果是不样的。此时整个人都不好了。以为掉了一个坑里面,这个vmp引擎是假的,再次分析程序,包括另外的一个libmyqtest.so,也没发现端倪,果断放弃。
5、作者在群里提示说
random的随机种子是固定的,我理解的是“对于正确的sn无论输入多少次,结果都应该是正确的“,看来作者的意图是第一次输入正确就算通过。再次开始分析。这里是造成多解的一个原因:第一次可以不正确,后面几次是可能正确的。
6、由于base64的基准是随机生成的,难免会有重复的,一般base64的基准字符是不重复的, 这样加密和解密才能对应。但是随机生成的基准字符有重复的造成加密和解密的结果是不一致的,因此对于重复的字符需要进行分别尝试,这也是可能造成多解的一个原因。
7、在java层对输入的flag使用了多种编码格式的转换,使得反推flag的时候花费了点时间。
二、流程分析
(一)java部分代码分析
1 java字符串解密
反编译后可以看到大部分字符串都是加密的,加密函数为:OooOO0OOli.d,为了快速定位到检测SN入口,需要将所有加密的字符串进行解密。使用C对dex文件中的加密字符串进行解密如下():
int readUnsignedLeb128(unsigned char** pStream)
{
unsigned char* ptr = *pStream;
int result = *(ptr++);
if (result > 0x7f) {
int cur = *(ptr++);
result = (result & 0x7f) | ((cur & 0x7f) << 7);
if (cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 14;
if (cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 21;
if (cur > 0x7f) {
/*
* Note: We don't check to see if cur is out of
* range here, meaning we tolerate garbage in the
* high four-order bits.
*/
cur = *(ptr++);
result |= cur << 28;
}
}
}
}
*pStream = ptr;
return result;
}
unsigned char* GetFileNameBuffer(unsigned char* fileName, int* size)
{
FILE *fd;
unsigned char *pName;
int sign = 0;
int filesize;
if (NULL == fileName)
{
return NULL;
}
fd = fopen((char*)fileName, "rb");
if (NULL == fd)
{
return NULL;
}
fseek(fd, 0, SEEK_END);
filesize = ftell(fd);
pName = (unsigned char*)malloc(filesize + 100);
if (NULL == pName)
{
fclose(fd);
return NULL;
}
memset(pName, 0, filesize + 100);
fseek(fd, 0, SEEK_SET);
fread(pName, 1, filesize, fd);
fclose(fd);
*size = filesize;
return pName;
}
bool WriteFileNameBuffer(const char* fileName, unsigned char* buf, int size)
{
FILE *fd;
if (NULL == fileName)
{
return false;
}
unlink((const char*)fileName);
fd = fopen((char*)fileName, "wb");
if (NULL == fd)
{
return NULL;
}
fwrite(buf, 1, (size_t)size, fd);
fclose(fd);
return true;
}
char dd1(char key)
{
if (key <= 0x39 && key >= 0x30)
return key - 0x30;
else if (key <= 'F' && key >= 'A')
{
return key - 0x37;
}
else
{
return -1;
}
}
bool isnNeedDecryptString(char* arg8,unsigned int len)
{
for (unsigned int i = 0; i < len; i++)
{
if (dd1(arg8[i]) < 0)
{
return false;
}
}
return true;
}
bool dexStringDecrypt(char* fileName, char* outFile)
{
int size;
//打开dex文件
unsigned char* pbuf1 = GetFileNameBuffer((unsigned char*)"fileName", &size);
if (NULL == pbuf1)
return false;
unsigned int stringCnt = *(unsigned int*)(pbuf1 + 0x38);
unsigned int stringOffset = *(unsigned int*)(pbuf1 + 0x3c);
unsigned int* stringId = (unsigned int*)(pbuf1 + stringOffset);
for (unsigned int i = 0; i < 2488; i++)
{
if (i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900)
continue;
unsigned int off = stringId[i];
unsigned char * pbuf = pbuf1 + off;
int len = readUnsignedLeb128(&pbuf);
if (len <3)
continue;
if (false == isnNeedDecryptString((char*)pbuf, len))
continue;
char* outString = decryptString((char*)pbuf, len);
if (NULL == outString)
continue;
printf("i = %d, out = %s\n",i, outString);
memcpy(pbuf, outString, strlen(outString));
}
WriteFileNameBuffer(outFile, (unsigned char*)pbuf1, size);
return true;
}
int readUnsignedLeb128(unsigned char** pStream)
{
unsigned char* ptr = *pStream;
int result = *(ptr++);
if (result > 0x7f) {
int cur = *(ptr++);
result = (result & 0x7f) | ((cur & 0x7f) << 7);
if (cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 14;
if (cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 21;
if (cur > 0x7f) {
/*
* Note: We don't check to see if cur is out of
* range here, meaning we tolerate garbage in the
* high four-order bits.
*/
cur = *(ptr++);
result |= cur << 28;
}
}
}
}
*pStream = ptr;
return result;
}
unsigned char* GetFileNameBuffer(unsigned char* fileName, int* size)
{
FILE *fd;
unsigned char *pName;
int sign = 0;
int filesize;
if (NULL == fileName)
{
return NULL;
}
fd = fopen((char*)fileName, "rb");
if (NULL == fd)
{
return NULL;
}
fseek(fd, 0, SEEK_END);
filesize = ftell(fd);
pName = (unsigned char*)malloc(filesize + 100);
if (NULL == pName)
{
fclose(fd);
return NULL;
}
memset(pName, 0, filesize + 100);
fseek(fd, 0, SEEK_SET);
fread(pName, 1, filesize, fd);
fclose(fd);
*size = filesize;
return pName;
}
bool WriteFileNameBuffer(const char* fileName, unsigned char* buf, int size)
{
FILE *fd;
if (NULL == fileName)
{
return false;
}
unlink((const char*)fileName);
fd = fopen((char*)fileName, "wb");
if (NULL == fd)
{
return NULL;
}
fwrite(buf, 1, (size_t)size, fd);
fclose(fd);
return true;
}
char dd1(char key)
{
if (key <= 0x39 && key >= 0x30)
return key - 0x30;
else if (key <= 'F' && key >= 'A')
{
return key - 0x37;
}
else
{
return -1;
}
}
bool isnNeedDecryptString(char* arg8,unsigned int len)
{
for (unsigned int i = 0; i < len; i++)
{
if (dd1(arg8[i]) < 0)
{
return false;
}
}
return true;
}
bool dexStringDecrypt(char* fileName, char* outFile)
{
int size;
//打开dex文件
unsigned char* pbuf1 = GetFileNameBuffer((unsigned char*)"fileName", &size);
if (NULL == pbuf1)
return false;
unsigned int stringCnt = *(unsigned int*)(pbuf1 + 0x38);
unsigned int stringOffset = *(unsigned int*)(pbuf1 + 0x3c);
unsigned int* stringId = (unsigned int*)(pbuf1 + stringOffset);
for (unsigned int i = 0; i < 2488; i++)
{
if (i == 144 || i == 2145 || i == 2146 || i == 2147 || i == 2148 || i == 2308 || i == 2309 || i == 1900)
continue;
unsigned int off = stringId[i];
unsigned char * pbuf = pbuf1 + off;
int len = readUnsignedLeb128(&pbuf);
if (len <3)
continue;
if (false == isnNeedDecryptString((char*)pbuf, len))
continue;
char* outString = decryptString((char*)pbuf, len);
if (NULL == outString)
continue;
printf("i = %d, out = %s\n",i, outString);
memcpy(pbuf, outString, strlen(outString));
}
WriteFileNameBuffer(outFile, (unsigned char*)pbuf1, size);
return true;
}
下面是部分结果:
i = 117, out = No keylines defined for
i = 193, out = Keyline index
i = 194, out = Keyframe
i = 195, out = Key error!
i = 297, out = Fragment no longer exists for key
i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS
i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS
i = 482, out = Bad fragment at key
i = 681, out = The key is correct and the decryption begins!
i = 758, out = Result key can't be null
i = 933, out = keyframe
i = 934, out = key == null
i = 935, out = key == null || value == null
i = 941, out = intent_extra_data_key
i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey:
i = 1182, out = android.support.groupKey
i = 1201, out = android.support.sortKey
i = 1241, out = action_key
i = 1397, out = resultKey
i = 117, out = No keylines defined for
i = 193, out = Keyline index
i = 194, out = Keyframe
i = 195, out = Key error!
i = 297, out = Fragment no longer exists for key
i = 302, out = FLAG_REQUEST_FILTER_KEY_EVENTS
i = 476, out = CAPABILITY_CAN_FILTER_KEY_EVENTS
i = 482, out = Bad fragment at key
i = 681, out = The key is correct and the decryption begins!
i = 758, out = Result key can't be null
i = 933, out = keyframe
i = 934, out = key == null
i = 935, out = key == null || value == null
i = 941, out = intent_extra_data_key
i = 1149, out = android.arch.lifecycle.ViewModelProvider.DefaultKey:
i = 1182, out = android.support.groupKey
i = 1201, out = android.support.sortKey
i = 1241, out = action_key
i = 1397, out = resultKey
2 manifest分析
从manifest文件可知:
a)没有application
b)主activity为:android.support.v4.app.o000000o
c)注册了一个provider:android.support.v4.app.OO0OOOO0
因为
provider会在入口类走之前运行,因此第一个执行的类为
android.support.v4.app.OO0OOOO0,正其父类android.support.v4.app.O0OO00O的init函数中加载‘libmydvp.so’
System.loadLibrary(OooOO0OOli.d("mydvpB393F"));
3、 android.support.v4.app.o000000o入口类
1)其init函数会加载 “libmyqtest.so
System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
public void onClick(View arg3) {
String v1;
O0OOOOO v3;
if(this.O000000o.O000oOo) {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E");
}
else if(OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())) {
this.O000000o.O000oOo = true;
Toast.makeText(this.O000000o.O0000oo0(), OooOO0OOli.d("The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"), 0).show();
this.O000000o.mybutton.setText(OooOO0OOli.d("In decryptionD361C1D260001"));
return;
}
else {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("Key error!423D201E48");
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
public static boolean O00000Oo(String randomString, String flag) {
byte[] key;
int index = 0;
if(TextUtils.isEmpty(((CharSequence)flag))) {
return 0;
}
int v2 = 1;
if((flag.startsWith(OooOO0OOli.d("flag{E2834"))) && (flag.endsWith(OooOO0OOli.d("32")))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) + "";
System.out.println(flag);
if(flag.startsWith(OooOO0OOli.d("62"))) {
flag = flag.substring(1);
}
new byte[0];
try {
key = flag.getBytes(OooOO0OOli.d("utf-896277"));
}
catch(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 = new StringBuilder();
int len = key.length;
while(index < len) {
int keyValue = key[index];
int v6 = keyValue - 48;
if(v6 > 9) {
v6 = keyValue - 65;
if(v6 <= 25) {
v6 += 10;
}
else if(keyValue == 125) {
v6 = 62;
}
else {
v6 += -61;
}
}
char v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if(v6 == 0) {
v5_1 = '+';
}
v3_1.append(v5_1);
if(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
2)调用OO0o0.O0000O0o(flag)函数。该函数实际上就是用输入的sn索引下面的表,然后转换成一个long型的字符串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字符串进行chartobyte后索引一个常量表:
OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
并且当时9的倍数是使用'+'字符代替,并且将原始的9个字符串拷贝到之后。
4) 调用
OO0o0.O00000Oo(v3_1.toString().getBytes())
这个函数实际上是个变形的base64。但是其基准字符是随机生成的,随机种子固定。部分代码如下:
for(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try {
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch(Throwable v2_3) {
v2_3.printStackTrace();
}
catch(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
unsigned char base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
System.loadLibrary(OooOO0OOli.d("mydvpB393F"));
3、 android.support.v4.app.o000000o入口类
1)其init函数会加载 “libmyqtest.so
System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
System.loadLibrary(OooOO0OOli.d("myqtestB2A433B"));
2)后面的流程就比较复杂,而且字符混淆非常严重,可以从输入提示入手,即 "Key error!", 可以检测函数在类android.support.v4.app.O000000o中,如下:
public void onClick(View arg3) {
String v1;
O0OOOOO v3;
if(this.O000000o.O000oOo) {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E");
}
else if(OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())) {
this.O000000o.O000oOo = true;
Toast.makeText(this.O000000o.O0000oo0(), OooOO0OOli.d("The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"), 0).show();
this.O000000o.mybutton.setText(OooOO0OOli.d("In decryptionD361C1D260001"));
return;
}
else {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("Key error!423D201E48");
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
public void onClick(View arg3) {
String v1;
O0OOOOO v3;
if(this.O000000o.O000oOo) {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("The decryption has already started! Please don\'t touch it!083D1B0A2B6E101F2309083C0A4F2B205E683B4C1D201A0C276F593B6E");
}
else if(OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())) {
this.O000000o.O000oOo = true;
Toast.makeText(this.O000000o.O0000oo0(), OooOO0OOli.d("The key is correct and the decryption begins!F3B27556F2B090A3D161F3B265F216F0E0C2806013C6E"), 0).show();
this.O000000o.mybutton.setText(OooOO0OOli.d("In decryptionD361C1D260001"));
return;
}
else {
v3 = this.O000000o.O0000oo0();
v1 = OooOO0OOli.d("Key error!423D201E48");
}
Toast.makeText(((Context)v3), ((CharSequence)v1), 0).show();
}
可以看出OO0o0.O00000Oo(o000000o.g_string2, this.O000000o.O0000O0o.getText().toString())函数返回真即为正确。其中参数
this.O000000o.O0000O0o.getText().toString()为输入的flag。下面继续跟进函数“
OO0o0.O00000Oo
” 2)检测函数
OO0o0.O00000Oo
public static boolean O00000Oo(String randomString, String flag) {
byte[] key;
int index = 0;
if(TextUtils.isEmpty(((CharSequence)flag))) {
return 0;
}
int v2 = 1;
if((flag.startsWith(OooOO0OOli.d("flag{E2834"))) && (flag.endsWith(OooOO0OOli.d("32")))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) + "";
System.out.println(flag);
if(flag.startsWith(OooOO0OOli.d("62"))) {
flag = flag.substring(1);
}
new byte[0];
try {
key = flag.getBytes(OooOO0OOli.d("utf-896277"));
}
catch(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 = new StringBuilder();
int len = key.length;
while(index < len) {
int keyValue = key[index];
int v6 = keyValue - 48;
if(v6 > 9) {
v6 = keyValue - 65;
if(v6 <= 25) {
v6 += 10;
}
else if(keyValue == 125) {
v6 = 62;
}
else {
v6 += -61;
}
}
char v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if(v6 == 0) {
v5_1 = '+';
}
v3_1.append(v5_1);
if(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
public static boolean O00000Oo(String randomString, String flag) {
byte[] key;
int index = 0;
if(TextUtils.isEmpty(((CharSequence)flag))) {
return 0;
}
int v2 = 1;
if((flag.startsWith(OooOO0OOli.d("flag{E2834"))) && (flag.endsWith(OooOO0OOli.d("32")))) {
flag = flag.substring(flag.indexOf(123) + 1, flag.length() - 1);
}
flag.getBytes();
flag = OO0o0.O0000O0o(flag) + "";
System.out.println(flag);
if(flag.startsWith(OooOO0OOli.d("62"))) {
flag = flag.substring(1);
}
new byte[0];
try {
key = flag.getBytes(OooOO0OOli.d("utf-896277"));
}
catch(UnsupportedEncodingException v3) {
v3.printStackTrace();
}
StringBuilder v3_1 = new StringBuilder();
int len = key.length;
while(index < len) {
int keyValue = key[index];
int v6 = keyValue - 48;
if(v6 > 9) {
v6 = keyValue - 65;
if(v6 <= 25) {
v6 += 10;
}
else if(keyValue == 125) {
v6 = 62;
}
else {
v6 += -61;
}
}
char v5_1 = OO0o0.O0000OOo[v6];
v6 = v2 % 9;
if(v6 == 0) {
v5_1 = '+';
}
v3_1.append(v5_1);
if(v6 == 0) {
keyValue = v2 / 9;
v3_1.append(flag.substring((keyValue - 1) * 9, keyValue * 9));
}
++v2;
++index;
}
return OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()));
}
这部分流程比较简单
1)分析输入的sn是否是flag{xxx}格式的如果是就截取xxx。
2)调用OO0o0.O0000O0o(flag)函数。该函数实际上就是用输入的sn索引下面的表,然后转换成一个long型的字符串。
OO0o0.O0000O0o = new char[]{'d', 'c', '2', 'l', '{', '}', 'f', 'g', 'e', 'm', 'a', 'b', 'h', '0', '8', 'y'};
3)再次使用long型的字符串进行chartobyte后索引一个常量表:
OO0o0.O0000OOo = new char[]{'加', '载', '本', '件', '请', '卸', '要', '微', '软', '不', '可', '以', '来', '去', '安', 'a', '人', '7', '减', '好', 'l', '卓', '测', 'p', '试', 'p', '3', '7', '乘', '吗', 'b', '桐', 'c', 'e', '眼', 'q', '6', '4', '以', '为', '神', 'd', '无', 'f', '功', '圣', '名', '至', '己', '0', '何', '解', '忧', 'g', '唯', '有', '1', '杜', '2', '康', 'h', '}', '{'};
并且当时9的倍数是使用'+'字符代替,并且将原始的9个字符串拷贝到之后。
4) 调用
OO0o0.O00000Oo(v3_1.toString().getBytes())
这个函数实际上是个变形的base64。但是其基准字符是随机生成的,随机种子固定。部分代码如下:
for(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try {
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch(Throwable v2_3) {
v2_3.printStackTrace();
}
catch(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
unsigned char base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
for(inputLen = 0; inputLen < OO0o0.seed.length; ++inputLen) {
try {
OO0o0.seed[inputLen] = ((byte)(OO0o0.O0000OoO.nextInt() % 256));
}
catch(Throwable v2_3) {
v2_3.printStackTrace();
}
catch(NumberFormatException v2_1) {
v2_1.printStackTrace();
}
}
对应第一次的生成的基准如下:
unsigned char base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
unsigned char base64char[65] =
{ 0xa9, 0x06, 0xab, 0x48, 0x03, 0x8b, 0x08, 0xf0, 0xe5, 0x38, 0x29, 0xe9, 0x2b, 0x7e, 0x26, 0x57,
0x36, 0x75, 0xa2, 0x4d, 0x10, 0x35, 0x98, 0x3a, 0xd4, 0x63, 0x5b, 0xa3, 0x4c, 0x0e, 0xd7, 0x12,
0xdb, 0xbb, 0x1c, 0x4e, 0x9d, 0xbe, 0x1d, 0xcb, 0xcc, 0xcb, 0xb4, 0x63, 0x65, 0xcc, 0xe8, 0x46,
0x0b, 0xf4, 0x72, 0x2f, 0x2a, 0x13, 0x7d, 0xd8, 0x5e, 0x2b, 0xae, 0xb0, 0x16, 0x44, 0x63, 0xca,
0x74 };
其中最后一个字符为站位字符其数值为0x74,这里注意的是再次调用次base64函数后,这个基准数值将发生变化,重新随机生成。
其中最后一个字符为站位字符其数值为0x74,这里注意的是再次调用次base64函数后,这个基准数值将发生变化,重新随机生成。
5)调用OO0o0.O000000o(randomString, OO0o0.O00000Oo(v3_1.toString().getBytes()))
以一个随机产生的字符串(无用的),以及base64结果作为参数调用函数
OO0o0.O000000o。其返回如果为true则flag正确。
4、函数 : public static boolean O000000o(String paramString1, String paramString2)
public static boolean O000000o(String paramString1, String paramString2)
{
int j;
try
{
paramString2 = paramString2.getBytes(OooOO0OOli.d("ISO-8859-1087A764158"));
j = paramString2.length;
i = 0;
}
catch (UnsupportedEncodingException paramString1)
{
paramString1.printStackTrace();
}
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
paramString2 = new OO00OO();
paramString2.O000000o = "";
int i = 0;
for (;;)
{
if (i < paramString1.length)
{
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append(paramString2.O000000o);
localStringBuilder.append(OO00OO.O00000Oo(paramString1[i]));
paramString2.O000000o = localStringBuilder.toString();
i += 1;
}
else
{
boolean bool = "820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3".equals(paramString2.O000000o);
return bool;
}
}
while (i < j)
{
int k = paramString2[i];
i += 1;
}
}
从上面的函数可以知道,最后结果为“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”时flag正确。
函数开始是进行了编码转换后执行如下语句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一个native函数,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
这个native函数输入的是一个base64加密后的结果。将其返回的结果进行bytetochar转换后与上面的标定字符串比较相等即为正确。因此后续核心分析转为native函数
OO0OOOO。首先要定位到此函数。
public static boolean O000000o(String paramString1, String paramString2)
{
int j;
try
{
paramString2 = paramString2.getBytes(OooOO0OOli.d("ISO-8859-1087A764158"));
j = paramString2.length;
i = 0;
}
catch (UnsupportedEncodingException paramString1)
{
paramString1.printStackTrace();
}
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
paramString2 = new OO00OO();
paramString2.O000000o = "";
int i = 0;
for (;;)
{
if (i < paramString1.length)
{
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append(paramString2.O000000o);
localStringBuilder.append(OO00OO.O00000Oo(paramString1[i]));
paramString2.O000000o = localStringBuilder.toString();
i += 1;
}
else
{
boolean bool = "820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3".equals(paramString2.O000000o);
return bool;
}
}
while (i < j)
{
int k = paramString2[i];
i += 1;
}
}
从上面的函数可以知道,最后结果为“820e52333de3bcb42467f0a20564c145af5edbf2e923df33be21f0af159710c92cbc43f79f94ec930a7ae86021af5b3ae263369299de5436b85f297be08a032a28dc357391961ecc26931bfc97d67a5e74d8781fb4105b9afbe613a2041dd8c3”时flag正确。
函数开始是进行了编码转换后执行如下语句
paramString1 = new OO0OOOO().OO0OOOO(paramString2, paramString1.getBytes());
其是一个native函数,原型如下:
public native byte[] OO0OOOO(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2);
这个native函数输入的是一个base64加密后的结果。将其返回的结果进行bytetochar转换后与上面的标定字符串比较相等即为正确。因此后续核心分析转为native函数
OO0OOOO。首先要定位到此函数。
1、初始化数组如下:
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1
.init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1
.init_array:000E0AE0 91 31 02 00 DCD init_nop+1
.init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1
.init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1
.init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1
.init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1
.init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1
.init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1
.init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1
.init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1
.init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1
.init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1
.init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1
.init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1
.init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1
.init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1
.init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
前面的几个函数都是解密字符串的,其中包括对native函数
OO0OOOO原型的解密。
2、反调试函数init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由于此函数使用ollvm混淆,并且加入了一些垃圾代码使得F5失效,简单写个脚本去掉相关垃圾代码,F5即可成功。如下
#coding=utf-8
import struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if BADADDR == ea:
break
patch_bytes(ea, '\xC0\x46' * len)
print 'ea = ' + str(hex(ea))
ea = ea + len
def KillNop():
lajiData = '2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData = '\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 = 'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 = '2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr('libmydvp.so')
startAddr = libBase
endAddr = libBase + 0x8916A
print 'startAddr= ' + str(hex(startAddr))
print 'endAddr = ' + str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
F5之后的代码也很多,将近2000行,只说下核心代码
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (void *(*)(void *))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((int)&v249, buf_1);
sub_A37CC((int)&v250, pid);
sub_951B4(&v249, &v230);
1、函数开始创建了一个线程antidebugThread1(174CC )这个线程后面在说。
init_array:000E0AD8 75 4A 01 00 DCD init_sshho+1
.init_array:000E0ADC 09 2A 02 00 DCD init_tracePidStringDecode+1
.init_array:000E0AE0 91 31 02 00 DCD init_nop+1
.init_array:000E0AE4 29 5B 07 00 DCD init_decryptString+1
.init_array:000E0AE8 E9 87 07 00 DCD init_decryptString2+1
.init_array:000E0AEC A1 5D 08 00 DCD init_decrypt3+1
.init_array:000E0AF0 F5 EF 08 00 DCD init_decrypt4+1
.init_array:000E0AF4 ED 04 09 00 DCD init_decrypt5+1
.init_array:000E0AF8 B9 05 09 00 DCD int_nop1+1
.init_array:000E0AFC E9 4A 09 00 DCD init_decrypt6+1
.init_array:000E0B00 C9 1B 01 00 DCD init_g_sspbahh1+1
.init_array:000E0B04 E9 AE 01 00 DCD init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal+1
.init_array:000E0B08 55 ED 01 00 DCD init_antiDebug2_fork+1
.init_array:000E0B0C CD 1B 01 00 DCD sub_11BCC+1
.init_array:000E0B10 A9 1C 01 00 DCD sub_11CA8+1
.init_array:000E0B14 85 1D 01 00 DCD sub_11D84+1
.init_array:000E0B18 C1 1D 01 00 DCD sub_11DC0+1
.init_array:000E0B1C D1 1D 01 00 DCD sub_11DD0+1
前面的几个函数都是解密字符串的,其中包括对native函数
OO0OOOO原型的解密。
2、反调试函数init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal(1AEE8)
由于此函数使用ollvm混淆,并且加入了一些垃圾代码使得F5失效,简单写个脚本去掉相关垃圾代码,F5即可成功。如下
#coding=utf-8
import struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if BADADDR == ea:
break
patch_bytes(ea, '\xC0\x46' * len)
print 'ea = ' + str(hex(ea))
ea = ea + len
def KillNop():
lajiData = '2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData = '\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 = 'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 = '2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr('libmydvp.so')
startAddr = libBase
endAddr = libBase + 0x8916A
print 'startAddr= ' + str(hex(startAddr))
print 'endAddr = ' + str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
#coding=utf-8
import struct
from idaapi import *
from idc import *
from idautils import *
def patchNopCode(ea, endAddr, data, len):
while ea < endAddr:
ea = find_binary(ea, SEARCH_DOWN| SEARCH_NEXT, data, radix=16)
if BADADDR == ea:
break
patch_bytes(ea, '\xC0\x46' * len)
print 'ea = ' + str(hex(ea))
ea = ea + len
def KillNop():
lajiData = '2D E9 F0 47 BD E8 F0 47 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47'
nopData = '\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46\xC0\x46'
nopCode1 = 'B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40 01 F1 01 01 A1 F1 01 01'
lajiData2 = '2D E9 F0 40 BD E8 F0 40 13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 14 E0 16 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 EF F3 00 84 10 B4 10 BC 84 F3 00 89 E2 E7 BD E8 B1 40 E7 E7 2D E9 F0 47 BD E8 F0 47 FF E7'
libBase = GetLibAddr('libmydvp.so')
startAddr = libBase
endAddr = libBase + 0x8916A
print 'startAddr= ' + str(hex(startAddr))
print 'endAddr = ' + str(hex(endAddr))
patchNopCode(startAddr, endAddr, lajiData, 35)
KillNop()
F5之后的代码也很多,将近2000行,只说下核心代码
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (void *(*)(void *))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((int)&v249, buf_1);
sub_A37CC((int)&v250, pid);
sub_951B4(&v249, &v230);
pthread_create((pthread_t *)&g_antiDebug_thread, 0, (void *(*)(void *))antidebugThread1, 0);
pid = getpid();
sub_B7BB0(&v230);
buf_1 = std::operator|(16, 8);
sub_A83CC((int)&v249, buf_1);
sub_A37CC((int)&v250, pid);
sub_951B4(&v249, &v230);
2、然后剩下的将近1800多行就是个tracepid反调试。
3、如果tracepid的值不为0 ,则执行如下代码:
if ( v161 != -942489079 )
break;
LODWORD(v181) = fd; // 反调试设置为1
HIDWORD(v181) = pid;
*(_QWORD *)&g_antiDebug_Global = v181;
v161 = -1048192996;
如果在调试状态下 全局变量g_antiDebug_Global(E3550)将被设置为文件句柄值,在
antidebugThread1会访问在这个值,实际上其应该是一个类似于jvm,env的指针,被设置成文件句柄后,会使程序访问其时发生异常崩溃。知道这些后过掉次函数反调试就比较容易了。(后面会有一个过掉所有反调试的脚步)
if ( v161 != -942489079 )
break;
LODWORD(v181) = fd; // 反调试设置为1
HIDWORD(v181) = pid;
*(_QWORD *)&g_antiDebug_Global = v181;
v161 = -1048192996;
如果在调试状态下 全局变量g_antiDebug_Global(E3550)将被设置为文件句柄值,在
antidebugThread1会访问在这个值,实际上其应该是一个类似于jvm,env的指针,被设置成文件句柄后,会使程序访问其时发生异常崩溃。知道这些后过掉次函数反调试就比较容易了。(后面会有一个过掉所有反调试的脚步)
3、反调试线程
antidebugThread1(174CC)
这个函数看上去依然很大(2000多行),起始代码也很简单,贴一下核心代码。
1)其会检索
g_antiDebug_Global(E3550)如果其值为0,则调用usleep随眠。
2)如果不为0,而是之前反调试的结果为文件句柄,将发生异常程序退出。
3)如果不为0,而是一个合法的值,执行如下代码:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)
+ 860))();
1)其会检索
g_antiDebug_Global(E3550)如果其值为0,则调用usleep随眠。
2)如果不为0,而是之前反调试的结果为文件句柄,将发生异常程序退出。
3)如果不为0,而是一个合法的值,执行如下代码:
nativeRegisterFlag = __PAIR__(realNativeFun, decryptString50((int)&v294));
(*(void (**)(void))(*(_DWORD *)((unsigned int)&dword_E3554 & ~dword_E3554 | dword_E3554 & ~(unsigned int)&dword_E3554)
+ 860))();
其中
realNativeFun(14A9C)实际上就是之前我们要寻找的native函数。但是由于
g_antiDebug_Global = 0,所以次部分代码没有执行。
4)剩下其他1800多号代码又是个tracepid反调试功能。与
init_antiDebug1_CreateThread_AndSet_g_antiDebugGlobal类似过掉方法也一样。
4、初始化函数init_antiDebug2_fork(1ED54)
核心代码如下:
newthread = &v410;
haystack = (char *)&v409;
v417 = (char *)&v409;
v416 = (char *)&v406;
v413 = &v405;
v421 = &v404;
v418 = (char *)&v404;
needle = (char *)&v403;
v419 = &v402;
v426 = &v401;
v410 = pthread_self();
pipe(&g_pipe);
antidebugptraceFork();
v392 = pthread_create(newthread, 0, (void *(*)(void *))sub_1D404, 0);
v393 = &v411;
v394 = &v409;
v395 = &savedregs;
v396 = -1357197669;
v22 = getpid();
newthread = &v410;
haystack = (char *)&v409;
v417 = (char *)&v409;
v416 = (char *)&v406;
v413 = &v405;
v421 = &v404;
v418 = (char *)&v404;
needle = (char *)&v403;
v419 = &v402;
v426 = &v401;
v410 = pthread_self();
pipe(&g_pipe);
antidebugptraceFork();
v392 = pthread_create(newthread, 0, (void *(*)(void *))sub_1D404, 0);
v393 = &v411;
v394 = &v409;
v395 = &savedregs;
v396 = -1357197669;
v22 = getpid();
1)创建线程sub_1D404(后面分析)
2) 创建一个管道,同时调用函数 antidebugptraceFork();
3)依然tracepid反调试,采用相同方法 bypass
4 antidebugptraceFork(1D580)
核心代码如下:
sprintf((char *)&v125, v24, pid);
sonPid = fork();
1)fork一个子进程
sprintf((char *)&v125, v24, pid);
sonPid = fork();
1)fork一个子进程
2)子进程调用 ptrace(0, 0, 0, 0, ststusBuf_2);
3)子进程读取tracepid,并将其值通过管道write给父进程:write(dword_E356C, &buf, 4u);
4)父进程流程比较简单,直接函数返回了。
5)父进程的线程
sub_1D404为读取管道,代码如下:
int sub_1D404()
{
signed int v0; // r0
int v1; // r1
__pid_t v2; // r0
int result; // r0
int v4; // [sp+8h] [bp-20h]
int v5; // [sp+Ch] [bp-1Ch]
v4 = -1;
close(dword_E356C);
read(g_pipe, &v4, 4u);
sleep(1u);
LABEL_3:
v0 = 1819773075;
while ( 1 )
{
v1 = v0 & 0x7FFFFFFF;
if ( (v0 & 0x7FFFFFFF) == 340946481 )
{
v4 = -1;
goto LABEL_3;
}
if ( v1 == 1314826255 )
break;
if ( v1 == 1819773075 )
{
read(g_pipe, &v4, 4u);
v0 = 1314826255;
if ( !v4 )
v0 = 340946481;
}
}
kill(g_sonPid, 9);
v2 = getpid();
kill(v2, 9);
result = _stack_chk_guard - v5;
if ( _stack_chk_guard == v5 )
result = 0;
return result;
}
了解了以上反调试功能后:
int sub_1D404()
{
signed int v0; // r0
int v1; // r1
__pid_t v2; // r0
int result; // r0
int v4; // [sp+8h] [bp-20h]
int v5; // [sp+Ch] [bp-1Ch]
v4 = -1;
close(dword_E356C);
read(g_pipe, &v4, 4u);
sleep(1u);
LABEL_3:
v0 = 1819773075;
while ( 1 )
{
v1 = v0 & 0x7FFFFFFF;
if ( (v0 & 0x7FFFFFFF) == 340946481 )
{
v4 = -1;
goto LABEL_3;
}
if ( v1 == 1314826255 )
break;
if ( v1 == 1819773075 )
{
read(g_pipe, &v4, 4u);
v0 = 1314826255;
if ( !v4 )
v0 = 340946481;
}
}
kill(g_sonPid, 9);
v2 = getpid();
kill(v2, 9);
result = _stack_chk_guard - v5;
if ( _stack_chk_guard == v5 )
result = 0;
return result;
}
了解了以上反调试功能后:
首先可以直接将父进程的线程
sub_1D404杀掉,让其直接返回。然后nop掉fork指令,并使其结果r0=0就可以。
至此,初始化函数就分析完了。
四、jni_onload函数
这个函数代码量相对较小,贴出来吧
int __fastcall JNI_OnLoad(int a1)
{
char v1; // r1
char v2; // r1
unsigned int v3; // r2
signed int v4; // r1
signed int v5; // r0
unsigned int v6; // r6
int v7; // r0
unsigned int v8; // r12
signed int v9; // r3
signed int v10; // r4
bool v11; // zf
signed int v12; // r3
int v13; // r0
struct globalInfo *v14; // r0
int v15; // r0
signed int v16; // r1
signed int v17; // r3
signed int v18; // r3
clock_t v19; // r0
signed int v20; // r0
signed int v21; // r2
struct _JNIEnv **v22; // r0
int *v23; // r1
struct _JNIEnv **v24; // r2
struct _JNIEnv *v25; // r0
clock_t clockTime2; // r0
int size; // r0
signed int v28; // r1
int result; // r0
int v30; // [sp-1Ch] [bp-84h]
unsigned int v31; // [sp+4h] [bp-64h]
int *g_dword_E35501; // [sp+8h] [bp-60h]
_DWORD *v33; // [sp+Ch] [bp-5Ch]
struct _JavaVM *JVM; // [sp+10h] [bp-58h]
char v35; // [sp+16h] [bp-52h]
char v36; // [sp+17h] [bp-51h]
struct _JNIEnv **v37; // [sp+18h] [bp-50h]
int *v38; // [sp+1Ch] [bp-4Ch]
char v39; // [sp+23h] [bp-45h]
clock_t clockTime1; // [sp+24h] [bp-44h]
int v41; // [sp+28h] [bp-40h]
v33 = &_stack_chk_guard;
v1 = 0;
if ( y_47 < 10 )
v1 = 1;
v36 = v1;
v2 = 0;
if ( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) )
v2 = 1;
v35 = v2;
JVM = (struct _JavaVM *)a1;
v3 = (unsigned int)&g_antiDebug_Global & 0x1A04BD11;
g_dword_E35501 = &g_antiDebug_Global;
v31 = (~(unsigned int)&g_antiDebug_Global & 0xE5FB42EE | (unsigned int)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE);
v4 = 1974858797;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
v5 = v4;
v6 = v3;
if ( v4 <= 286463644 )
break;
if ( v4 > 1387993772 )
{
if ( v4 > 1974858796 )
{
if ( v4 == 2007388415 )
{
v4 = 102368831;
}
else if ( v4 == 1974858797 )
{
v4 = 1146459914;
if ( v36 )
v4 = -217046703;
if ( !v35 )
v4 = 1146459914;
if ( v35 != v36 )
v4 = -217046703;
}
}
else if ( v4 == 1387993773 )
{
v3 = -1;
v4 = -1667146202;
}
else if ( v4 == 1813294739 )
{
v16 = 0;
if ( y_47 < 10 )
v16 = 1;
v17 = 0;
if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v17 = 1;
v11 = v16 == v17;
v18 = 407986737;
if ( !v11 )
v18 = -1082095904;
v4 = v18;
if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v4 = -1082095904;
if ( y_47 >= 10 )
v4 = v18;
}
}
else if ( v4 <= 719945599 )
{
if ( v4 == 286463645 )
{
v4 = 0xEFC89CB9;
if ( v39 )
v4 = 0x52BB1AAD;
}
else if ( v4 == 407986737 )
{
v4 = -1082095904;
}
}
else
{
v3 = 65540;
v4 = -1667146202;
if ( v5 != 719945600 )
{
if ( v5 != 1146459914 )
{
v28 = 795111493;
goto LABEL_92;
}
v30 = 0;
getENV(JVM);
v3 = v6;
v4 = -217046703;
}
}
}
if ( v4 <= -770792315 )
break;
if ( v4 > -123417836 )
{
if ( v4 == 0xF8A4CB15 )
{
size = readSSSAndGetSize(g_apkPath, (int)algn_E353C);
v4 = -1557264936;
if ( !size )
v4 = 1813294739;
g_sssSize = size;
v3 = v6;
}
else if ( v4 == 102368831 )
{
v19 = clock();
v4 = 0x2AE97F80;
if ( (double)(signed int)(v19 - clockTime1) / 1000000.0 > 5.0 )
v4 = 0xD20EA486;
v3 = v6;
}
}
else if ( v4 == -272065351 )
{
clockTime1 = clock();
callnativeRegister(*v37);
v22 = v37;
v23 = g_dword_E35501;
*g_dword_E35501 = v31;
v24 = v37;
v23[1] = (unsigned int)(v23 + 1) & ~(unsigned int)*v22 | (unsigned int)*v22 & ~(unsigned int)(v23 + 1);
v25 = *v24;
g_apkPath = (int)GetApkPath();
clockTime2 = clock();
v4 = 0xF8A4CB15;
if ( (double)(signed int)(clockTime2 - clockTime1) / 1000000.0 > 2.0 )
v4 = 0x2F647045;
v3 = v6;
}
else if ( v4 == -217046703 )
{
v37 = (struct _JNIEnv **)&v30;
v38 = &v30;
v30 = 0;
v7 = getENV(JVM);
v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46;
v9 = 0;
if ( !v8 )
v9 = 1;
v10 = 0;
if ( y_47 < 10 )
v10 = 1;
v11 = v10 == v9;
v12 = 1146459914;
if ( !v11 )
v12 = 286463645;
v4 = v12;
if ( !v8 )
v4 = 286463645;
if ( v7 )
LOBYTE(v7) = 1;
v39 = v7;
if ( y_47 >= 10 )
v4 = v12;
v3 = v6;
}
else
{
v28 = -770792314;
LABEL_92:
v11 = v5 == v28;
v3 = v6;
v4 = v5;
if ( v11 )
abort();
}
}
if ( v4 <= -1509585911 )
break;
v4 = 102368831;
if ( v5 != -1509585910 )
{
v4 = v5;
if ( v5 == -1082095904 )
{
v4 = 407986737;
if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v4 = -1509585910;
if ( y_47 >= 10 )
v4 = 407986737;
v20 = 0;
if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v20 = 1;
v21 = 0;
if ( y_47 < 10 )
v21 = 1;
if ( v21 != v20 )
v4 = -1509585910;
v3 = v6;
}
}
}
if ( v4 == -1667146202 )
break;
if ( v4 == 2737702360 )
{
v13 = operator new(0x84u);
v14 = (struct globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize);
v4 = 2007388415;
if ( v15 )
v4 = 102368831;
v3 = v6;
}
}
result = *v33 - v41;
if ( *v33 == v41 )
result = v3;
return result;
}
1、第一部分
1) 获得env:etENV(JVM);
int __fastcall JNI_OnLoad(int a1)
{
char v1; // r1
char v2; // r1
unsigned int v3; // r2
signed int v4; // r1
signed int v5; // r0
unsigned int v6; // r6
int v7; // r0
unsigned int v8; // r12
signed int v9; // r3
signed int v10; // r4
bool v11; // zf
signed int v12; // r3
int v13; // r0
struct globalInfo *v14; // r0
int v15; // r0
signed int v16; // r1
signed int v17; // r3
signed int v18; // r3
clock_t v19; // r0
signed int v20; // r0
signed int v21; // r2
struct _JNIEnv **v22; // r0
int *v23; // r1
struct _JNIEnv **v24; // r2
struct _JNIEnv *v25; // r0
clock_t clockTime2; // r0
int size; // r0
signed int v28; // r1
int result; // r0
int v30; // [sp-1Ch] [bp-84h]
unsigned int v31; // [sp+4h] [bp-64h]
int *g_dword_E35501; // [sp+8h] [bp-60h]
_DWORD *v33; // [sp+Ch] [bp-5Ch]
struct _JavaVM *JVM; // [sp+10h] [bp-58h]
char v35; // [sp+16h] [bp-52h]
char v36; // [sp+17h] [bp-51h]
struct _JNIEnv **v37; // [sp+18h] [bp-50h]
int *v38; // [sp+1Ch] [bp-4Ch]
char v39; // [sp+23h] [bp-45h]
clock_t clockTime1; // [sp+24h] [bp-44h]
int v41; // [sp+28h] [bp-40h]
v33 = &_stack_chk_guard;
v1 = 0;
if ( y_47 < 10 )
v1 = 1;
v36 = v1;
v2 = 0;
if ( !((~-x_46 * x_46 ^ 0xFFFFFFFE) & ~-x_46 * x_46) )
v2 = 1;
v35 = v2;
JVM = (struct _JavaVM *)a1;
v3 = (unsigned int)&g_antiDebug_Global & 0x1A04BD11;
g_dword_E35501 = &g_antiDebug_Global;
v31 = (~(unsigned int)&g_antiDebug_Global & 0xE5FB42EE | (unsigned int)&g_antiDebug_Global & 0x1A04BD11) ^ (a1 & 0x1A04BD11 | ~a1 & 0xE5FB42EE);
v4 = 1974858797;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
v5 = v4;
v6 = v3;
if ( v4 <= 286463644 )
break;
if ( v4 > 1387993772 )
{
if ( v4 > 1974858796 )
{
if ( v4 == 2007388415 )
{
v4 = 102368831;
}
else if ( v4 == 1974858797 )
{
v4 = 1146459914;
if ( v36 )
v4 = -217046703;
if ( !v35 )
v4 = 1146459914;
if ( v35 != v36 )
v4 = -217046703;
}
}
else if ( v4 == 1387993773 )
{
v3 = -1;
v4 = -1667146202;
}
else if ( v4 == 1813294739 )
{
v16 = 0;
if ( y_47 < 10 )
v16 = 1;
v17 = 0;
if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v17 = 1;
v11 = v16 == v17;
v18 = 407986737;
if ( !v11 )
v18 = -1082095904;
v4 = v18;
if ( (~(x_46 * (x_46 - 1)) | 0xFFFFFFFE) == -1 )
v4 = -1082095904;
if ( y_47 >= 10 )
v4 = v18;
}
}
else if ( v4 <= 719945599 )
{
if ( v4 == 286463645 )
{
v4 = 0xEFC89CB9;
if ( v39 )
v4 = 0x52BB1AAD;
}
else if ( v4 == 407986737 )
{
v4 = -1082095904;
}
}
else
{
v3 = 65540;
v4 = -1667146202;
if ( v5 != 719945600 )
{
if ( v5 != 1146459914 )
{
v28 = 795111493;
goto LABEL_92;
}
v30 = 0;
getENV(JVM);
v3 = v6;
v4 = -217046703;
}
}
}
if ( v4 <= -770792315 )
break;
if ( v4 > -123417836 )
{
if ( v4 == 0xF8A4CB15 )
{
size = readSSSAndGetSize(g_apkPath, (int)algn_E353C);
v4 = -1557264936;
if ( !size )
v4 = 1813294739;
g_sssSize = size;
v3 = v6;
}
else if ( v4 == 102368831 )
{
v19 = clock();
v4 = 0x2AE97F80;
if ( (double)(signed int)(v19 - clockTime1) / 1000000.0 > 5.0 )
v4 = 0xD20EA486;
v3 = v6;
}
}
else if ( v4 == -272065351 )
{
clockTime1 = clock();
callnativeRegister(*v37);
v22 = v37;
v23 = g_dword_E35501;
*g_dword_E35501 = v31;
v24 = v37;
v23[1] = (unsigned int)(v23 + 1) & ~(unsigned int)*v22 | (unsigned int)*v22 & ~(unsigned int)(v23 + 1);
v25 = *v24;
g_apkPath = (int)GetApkPath();
clockTime2 = clock();
v4 = 0xF8A4CB15;
if ( (double)(signed int)(clockTime2 - clockTime1) / 1000000.0 > 2.0 )
v4 = 0x2F647045;
v3 = v6;
}
else if ( v4 == -217046703 )
{
v37 = (struct _JNIEnv **)&v30;
v38 = &v30;
v30 = 0;
v7 = getENV(JVM);
v8 = (x_46 * ~-x_46 ^ 0xFFFFFFFE) & x_46 * ~-x_46;
v9 = 0;
if ( !v8 )
v9 = 1;
v10 = 0;
if ( y_47 < 10 )
v10 = 1;
v11 = v10 == v9;
v12 = 1146459914;
if ( !v11 )
v12 = 286463645;
v4 = v12;
if ( !v8 )
v4 = 286463645;
if ( v7 )
LOBYTE(v7) = 1;
v39 = v7;
if ( y_47 >= 10 )
v4 = v12;
v3 = v6;
}
else
{
v28 = -770792314;
LABEL_92:
v11 = v5 == v28;
v3 = v6;
v4 = v5;
if ( v11 )
abort();
}
}
if ( v4 <= -1509585911 )
break;
v4 = 102368831;
if ( v5 != -1509585910 )
{
v4 = v5;
if ( v5 == -1082095904 )
{
v4 = 407986737;
if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v4 = -1509585910;
if ( y_47 >= 10 )
v4 = 407986737;
v20 = 0;
if ( !(x_46 * (x_46 - 1) & (x_46 * (x_46 - 1) ^ 0xFFFFFFFE)) )
v20 = 1;
v21 = 0;
if ( y_47 < 10 )
v21 = 1;
if ( v21 != v20 )
v4 = -1509585910;
v3 = v6;
}
}
}
if ( v4 == -1667146202 )
break;
if ( v4 == 2737702360 )
{
v13 = operator new(0x84u);
v14 = (struct globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize);
v4 = 2007388415;
if ( v15 )
v4 = 102368831;
v3 = v6;
}
}
result = *v33 - v41;
if ( *v33 == v41 )
result = v3;
return result;
}
1、第一部分
1) 获得env:etENV(JVM);
2
)
获得当前clockTime1 = clock();
3
)
调用函数
callnativeRegister(*v37);
4
)
调用g_apkPath = (int)GetApkPath();
5
)
调用clockTime2 = clock();
6
)
执行
clockTime2-
clockTime1
貌似有个 时间反调试,为了方便,直接修改clock函数返回值即可。
2、
callnativeRegister函数(16CFC )
unsigned int __fastcall callnativeRegister(struct _JNIEnv *env)
{
char v1; // r2
char v2; // r1
unsigned int result; // r0
signed int v4; // r1
signed int v5; // r2
unsigned int v6; // r12
signed int v7; // r3
signed int v8; // r1
bool v9; // zf
signed int v10; // r3
struct _JNIEnv *env1; // [sp+Ch] [bp-24h]
unsigned __int8 v12; // [sp+11h] [bp-1Fh]
char v13; // [sp+12h] [bp-1Eh]
unsigned __int8 v14; // [sp+13h] [bp-1Dh]
env1 = env;
v1 = 0;
v2 = 0;
if ( y_45 < 10 )
v2 = 1;
v13 = v2;
result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1;
if ( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 )
v1 = 1;
v12 = v1;
v4 = -1230185785;
do
{
while ( 1 )
{
while ( 1 )
{
while ( v4 <= 1146544159 )
{
switch ( v4 )
{
case -1230185785:
result = v12;
v5 = 626878466;
if ( v12 != (unsigned __int8)v13 )
v5 = 1146544160;
v4 = v5;
if ( v13 )
v4 = 1146544160;
if ( !v12 )
v4 = v5;
break;
case -299872326:
result = v14;
v4 = 1603580720;
if ( v14 )
v4 = 1673631727;
break;
case 626878466:
result = nativeRegister(env1);
v4 = 1146544160;
break;
}
}
if ( v4 != 1146544160 )
break;
result = nativeRegister(env1);
v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44;
v7 = 0;
if ( !v6 )
v7 = 1;
v8 = 0;
if ( y_45 < 10 )
v8 = 1;
v9 = v8 == v7;
v10 = 626878466;
if ( !v9 )
v10 = -299872326;
v4 = v10;
if ( !v6 )
v4 = -299872326;
v14 = result;
if ( y_45 >= 10 )
v4 = v10;
}
if ( v4 != 1603580720 )
break;
v4 = 1673631727;
}
}
while ( v4 != 1673631727 );
return result;
}
看出是一个native注册函数,其又调用152C0进行真正的nativeregister,其部分代码如下:
v73 = NewGlobalRef(&env1->functions, v107);
dword_E3558 = (unsigned int)&dword_E3558 & ~v73 | v73 & ~(unsigned int)&dword_E3558;
RegisterNatives(&env1->functions, v107, v106, 1);
其实这个函数是个虚假的native函数。这里就不分析了。
3、第二部分
创建一个结构体 ,bin进行初始化
v13 = operator new(0x84u);
v14 = (struct globalInfo *)sub_85FB0(v13);
g_globalInfo = v14;
v15 = initSSSGlobal((int *)v14, *(int *)algn_E353C, g_sssSize);
unsigned int __fastcall callnativeRegister(struct _JNIEnv *env)
{
char v1; // r2
char v2; // r1
unsigned int result; // r0
signed int v4; // r1
signed int v5; // r2
unsigned int v6; // r12
signed int v7; // r3
signed int v8; // r1
bool v9; // zf
signed int v10; // r3
struct _JNIEnv *env1; // [sp+Ch] [bp-24h]
unsigned __int8 v12; // [sp+11h] [bp-1Fh]
char v13; // [sp+12h] [bp-1Eh]
unsigned __int8 v14; // [sp+13h] [bp-1Dh]
env1 = env;
v1 = 0;
v2 = 0;
if ( y_45 < 10 )
v2 = 1;
v13 = v2;
result = (~((x_44 - 1) * x_44) | 0xFFFFFFFE) + 1;
if ( (~((x_44 - 1) * x_44) | 0xFFFFFFFE) == -1 )
v1 = 1;
v12 = v1;
v4 = -1230185785;
do
{
while ( 1 )
{
while ( 1 )
{
while ( v4 <= 1146544159 )
{
switch ( v4 )
{
case -1230185785:
result = v12;
v5 = 626878466;
if ( v12 != (unsigned __int8)v13 )
v5 = 1146544160;
v4 = v5;
if ( v13 )
v4 = 1146544160;
if ( !v12 )
v4 = v5;
break;
case -299872326:
result = v14;
v4 = 1603580720;
if ( v14 )
v4 = 1673631727;
break;
case 626878466:
result = nativeRegister(env1);
v4 = 1146544160;
break;
}
}
if ( v4 != 1146544160 )
break;
result = nativeRegister(env1);
v6 = (x_44 * ~-x_44 ^ 0xFFFFFFFE) & x_44 * ~-x_44;
v7 = 0;
if ( !v6 )
v7 = 1;
v8 = 0;
if ( y_45 < 10 )
v8 = 1;
v9 = v8 == v7;
v10 = 626878466;
if ( !v9 )
v10 = -299872326;
v4 = v10;
if ( !v6 )
v4 = -299872326;
v14 = result;
if ( y_45 >= 10 )
v4 = v10;
}
if ( v4 != 1603580720 )
break;
v4 = 1673631727;
}
}
while ( v4 != 1673631727 );
return result;
}
看出是一个native注册函数,其又调用152C0进行真正的nativeregister,其部分代码如下:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-9-24 20:26
被oooAooo编辑
,原因: