本文为看雪论坛精华文章
看雪论坛作者ID:白云精灵
package cn.pojie52.cm01;//当前类MainActivity所在的包,也就是文件夹
import android.os.Bundle;//导入Bundle相关方法,变量
import android.view.View.OnClickListener;//导入按钮点击事件相关方法,变量
import android.view.View;//导入视图相关方法,变量。
import android.widget.EditText;//导入编辑框相关方法,变量
import android.widget.Toast;//导入吐司弹窗相关方法,变量
import androidx.appcompat.app.AppCompatActivity;//主类,一个activity必须要继承一个activity父类
//加载so文件的方法。
//static{}为静态代码块,在MainActivity创建时会优先执行里面的方法
//调用System对象里面的LoadLibrary方法,传入so文件的名字,去除lib,.so,后的名字
//在调用LoadLibrary方法后,底层会把lib,so进行拼接,然后去lib目录里寻找相关的so进行加载
static {
System.loadLibrary("native-lib");
}
//如下是一个native方法,返回值为Boolean类型,也就是true和false,
//方法名为check,参数为String类型
//因为是native方法,所以实现逻辑必定在so里面,而so里面的代码是由c或者c++编译的
//所以要执行这个System.loadLibrary("native-lib");代码,把so文件提前加载到内存中
public native boolean check(String arg1) {
}
@Override // androidx.appcompat.app.AppCompatActivity
//@Override说明这是一个重写方法,
//这个方法OnCreate在AppCompatActivity类里
//因为主类MainActivity后面加了extends AppCompatActivity
//说明AppCompatActivity里的方法,变量都会继承过来,也就是说我们可以直接使用
//参数为Bundle
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);//调用父类的OnCreate方法放入arg3参数
//父类也就是AppCompatActivity
this.setContentView(0x7F0A001C); // layout:activity_main
//调用setContentView方法初始化界面
//也就是我们在开始的时候看到的那个界面,
//有个编辑框,一个验证按钮
EditText v3 = (EditText)this.findViewById(0x7F070058); // id:flag
//调用findViewById方法传入编辑框的id,然后转为EditText,
//因为获取到的是一个view对象,这个view对象范围太大了
//我们直接转为EditText方便些
this.findViewById(0x7F070045).setOnClickListener(new View.OnClickListener() { // id:check
//这个是一种链式编程写法,简便,省去了定义变量来接收他
//这个是调用findViewById传入按钮的id,
//this指的是当前activity,因为继承了AppCompatActivity类
//所以我们可以直接使用里面的findViewById方法
//调用这个方法后返回的是一个对象,然后调用setOnclickListener方法
//传入匿名内部类对象,给按钮绑定一个监听事件
//onClick方法是重写的
@Override // android.view.View$OnClickListener
public void onClick(View arg4) {
}
});
}
在Onclick方法里的代码逻辑
String v4 = v3.getText().toString().trim();
//v3为编辑框,获取编辑框里面的信息,转为string,去除空格,
//赋值为string变量v4
if(v4.length() != 30) {
//判断v4的长度是否等于30
//如果不等于就提示flag格式错误,请重试
//return为返回
Toast.makeText(MainActivity.this, "flag格式错误,请重试", 0).show();
//调用Toast对象里面的makeText方法,放入MainActiviy
//因为onclick方法在内部类里面,所以是MainActivity.this,而不是this.MainActiviy
return;
}
if(MainActivity.this.check(v4)) {//调用native层的check方法,传入v4字符串
Toast.makeText(MainActivity.this, "恭喜你,验证正确!", 0).show();
//如果check方法的返回值为true那么弹出恭喜你,验证正确的提示
return;
}
Toast.makeText(MainActivity.this, "flag错误,再接再厉", 0).show();
//否则提示flag错误,再接再厉
}
__int64 __fastcall Java_cn_pojie52_cm01_MainActivity_check(_JNIEnv *a1, jobject a2, jstring a3)
{
const char *v5; // x21
size_t v6; // w0
int v7; // w0
__int64 v8; // x0
_BYTE *v9; // x0
int8x16_t v10; // q0
int8x16_t v11; // q4
int8x16_t v12; // q2
int8x16_t v13; // q5
int8x16_t v14; // q1
int8x16_t v15; // q0
__int64 v16; // x8
unsigned int v17; // w19
_BYTE v19[33]; // [xsp+0h] [xbp-A0h]
int v20; // [xsp+21h] [xbp-7Fh]
char v21; // [xsp+25h] [xbp-7Bh]
char v22; // [xsp+26h] [xbp-7Ah]
char v23; // [xsp+27h] [xbp-79h]
char v24; // [xsp+28h] [xbp-78h]
char dest[16]; // [xsp+38h] [xbp-68h] BYREF
__int128 v26; // [xsp+48h] [xbp-58h]
__int128 v27; // [xsp+58h] [xbp-48h]
__int128 v28; // [xsp+68h] [xbp-38h]
__int64 v29; // [xsp+78h] [xbp-28h]
v29 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( a1->functions->GetStringUTFLength((JNIEnv *)a1, a3) == 30 )
{
v5 = a1->functions->GetStringUTFChars(a1, a3, 0LL);
v28 = 0u;
v27 = 0u;
v26 = 0u;
*(_OWORD *)dest = 0u;
v6 = strlen(v5);
strncpy(dest, v5, v6);
a1->functions->ReleaseStringUTFChars((JNIEnv *)a1, a3, v5);
v7 = strlen(dest);
sub_B90((int)dest, v7, "areyousure??????");
v8 = strlen(dest);
v9 = (_BYTE *)sub_D90(dest, v8);
*(_OWORD *)v19 = unk_11A1;
*(_OWORD *)&v19[16] = unk_11B1;
*(_QWORD *)&v19[25] = unk_11BA;
v10.n128_u64[0] = 0xB2B2B2B2B2B2B2B2LL;
v10.n128_u64[1] = 0xB2B2B2B2B2B2B2B2LL;
v11.n128_u64[0] = 0xFEFEFEFEFEFEFEFELL;
v11.n128_u64[1] = 0xFEFEFEFEFEFEFEFELL;
v19[0] = 53;
v12 = veorq_s8(
vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[1], v10), (int8x16_t)xmmword_1130), (int8x16_t)xmmword_1140),
v11);
v13.n128_u64[0] = 0x101010101010101LL;
v13.n128_u64[1] = 0x101010101010101LL;
v14.n128_u64[0] = 0x3E3E3E3E3E3E3E3ELL;
v14.n128_u64[1] = 0x3E3E3E3E3E3E3E3ELL;
*(int8x16_t *)&v19[1] = vaddq_s8(
veorq_s8(
vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v12, 7uLL), vshlq_n_s8(v12, 1uLL))),
(int8x16_t)xmmword_1150),
v14);
v20 = 1782990162;
v15 = veorq_s8(
vaddq_s8(veorq_s8(vaddq_s8(*(int8x16_t *)&v19[17], v10), (int8x16_t)xmmword_1160), (int8x16_t)xmmword_1170),
v11);
v21 = ((1
- ((2 * ((((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C6 - 78) ^ 0xB2) - 117) ^ 0xFE) & 0x80) != 0))) ^ 0x25)
+ 62;
v16 = 0LL;
v22 = ((1
- ((2 * ((((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C7 - 78) ^ 0xB1) - 118) ^ 0xFE) & 0x80) != 0))) ^ 0x26)
+ 62;
*(int8x16_t *)&v19[17] = vaddq_s8(
veorq_s8(
vsubq_s8(v13, vorrq_s8(vshrq_n_u8(v15, 7uLL), vshlq_n_s8(v15, 1uLL))),
(int8x16_t)xmmword_1180),
v14);
v23 = ((1
- ((2 * ((((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C8 - 78) ^ 0xB0) - 119) ^ 0xFE) & 0x80) != 0))) ^ 0x27)
+ 62;
v24 = ((1
- ((2 * ((((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE)) | ((((unsigned __int8)(((unk_11C9 - 78) ^ 0xBF) - 120) ^ 0xFE) & 0x80) != 0))) ^ 0x28)
+ 62;
while ( v9[v16] == v19[v16] )
{
if ( v9[v16] )
{
if ( ++v16 != 41 )
continue;
}
v17 = 1;
goto LABEL_9;
}
v17 = 0;
LABEL_9:
free(v9);
}
else
{
return 0;
}
return v17;
}
//判断password的长度是否为30
if ( env->functions->GetStringUTFLength((JNIEnv *)env, password) == 30 )
//这个env是一个指针,指针里面有一个函数指针,函数指针里面有一个GetStringUTFLength函数
//这个函数用于判断字符串的长度
//这个涉及到了结构体相关知识
//把password转为c语言中的char类型
env->functions->GetStringUTFChars(env, password, 0LL);
password_length = strlen(password_);//获取password_的长度
//把password_拷贝到password__里面,拷贝长度为password_length
strncpy(password__, password_, password_length);
//释放password_指向的字符串空间
//也就是归还空间,让其他程序使用
env->functions->ReleaseStringUTFChars((JNIEnv *)env, password, password_);
//获取password__的长度
password__length = strlen(password__);
char password__[16]
//获取threeStr的长度
threeStrLen = strlen(threeStr);
do
{
v9 = *((unsigned __int8 *)v20 + v7);
//v7加上转换为无符号int类型的指针v20,也就是加上无符号int类型的步长。
然后取*取出相加后里面空间的值
v10 = v8 + v9 + (unsigned __int8)threeStr[v7 % threeStrLen];
//v9加上v8然后加上无符号整型的threeStr[v7%threeStrlen];
//对v7以threeStrlen进行取余,然后threeStr[取余的值]取出threeStr里面的字符串数据
v11 = v10 + 255;//v10加上255赋值给v11
if ( v10 >= 0 )//如果v10大于等于0就执行v10赋值给v11的操作
v11 = v10;把v10赋值给v11
v8 = v10 - (v11 & 0xFFFFFF00);//对v11与0XFFFFFF00进行与运算,然后v10减去运算结果,赋值给v8
*((_BYTE *)v20 + v7++) = *((_BYTE *)v20 + v8);
//把v20转为Byte*类型然后加上v8,步长为Byte *的长度,然后*取值
//然后把值赋值给把v20转为Byte*类型加上自增的v7,获得值后进行取*,赋值给这个取*花后的值
*((_BYTE *)v20 + v8) = v9;
//把v9赋值给把v20转为Byte*类型然后加上v8个Byte*步长里面的值
}
while ( v7 != 256 );如果v7不等于256就一直循环
//判断如果password__length是否为空
//在c语言中,非0就是true,也就是说可以用于判断是否为空
if ( password__length )
{
v12 = 0;//给v12赋值0
v13 = 0;给v13赋值0
v14 = password__length;//把密码长度赋值给v14
do
{
v15 = v12 + 1; //把v12加上1的值赋值给v15
if ( v12 + 1 >= 0 )//判断v12+1是否大于等于0
v16 = v12 + 1;//把v12加1的值赋值给v16
else
v16 = v12 + 256;//把v12加上256赋值给v16
v12 = v15 - (v16 & 0xFFFFFF00);//对v16与0XFFFFFF00进行与运算,然后v15减去运算结果,赋值给v12
v17 = *((unsigned __int8 *)v20 + v12);//把v20转为无符号int整型指针然后加上这个指针的步长,
//对这个值进行取星花赋值给v17
v18 = v13 + v17;//把v13加上v17的值赋值给v18
v19 = v18 + 255;//把v18加上255的值赋值给v19
if ( v18 >= 0 )//判断v18是否大于0
v19 = v18;//如果大于0就把v18赋值给v19
v13 = v18 - (v19 & 0xFFFFFF00);//对v19与0XFFFFFF00进行与运算,然后v18减去运算结果,赋值给v13
--v14;//v14减一
*((_BYTE *)v20 + v12) = *((_BYTE *)v20 + v13);
//把v20转为Byte*类型加上v13个Byte*的步长,然后取*取出里面的值,
//把这个值赋值给v20转为Byte*类型加上v12个Byte*步长的地址空间
*((_BYTE *)v20 + v13) = v17;
//把v17的值赋值给v20转为Byte*类型加上v13个Byte*步长的地址空间
*password__++ ^= *((_BYTE *)v20 + (unsigned __int8)(*((_BYTE *)v20 + v12) + v17));
//取出password里面的值
//把v20转为Byte*类型,加上把V20转为Byte*类型后加上v12进行取*加上v17后转为无符号int类型
//然后进行取*取出里面的值,把值与password进行异或然后赋值给password
}
while ( v14 );//如果v14不为空就一直循环
}
while ( v9[v16] == v19[v16] ) //循环对比v9[v16]的值与v19[v16]的值
{
if ( v9[v16] ) //判断v9[16]里是否有值
{
if ( ++v16 != 41 )//如果v16加一后不等于41就continue跳过代码
continue;
}
v17 = 1;//如果为1那么flag正确
goto LABEL_9;//跳到LABEL_9的位置
}
v17 = 0;//如果为0那么flag错误
LABEL_9:
free(v9);//释放内存
}
else
{
return 0;
}
return v17;//影响验证结果
import frida, sys//因为是frida hook,所以要导入frida模块,
//读取系统输入需要sys模块
jscode = '''
function inline_hook() {
var so_addr = Module.findBaseAddress("libnative-lib.so");
if (so_addr) {
console.log("so_addr:", so_addr);
var addr_b90 = so_addr.add(0xb90);
var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
var arg2 = 30;
var arg3 = Memory.allocUtf8String('areyousure??????');
var ret_b90 = sub_b90(arg1,arg2,arg3);
console.log(Memory.readByteArray(arg1,64));
var addr_d90 = so_addr.add(0xd90);
var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
var arg2 = 30;
var ret_d90 = sub_d90(arg1,arg2);
console.log(Memory.readByteArray(ret_d90,64));
}
}
setImmediate(inline_hook)
'''
def on_message(message, data):
if message['type'] == 'send':
print(" {0}".format(message['payload']))
else:
print(message)
pass
#print(frida.enumerate_devices())
# 查找USB设备并附加到目标进程
device = frida.get_remote_device()
#pid = device.spawn(["com.live.xctv"])
#session = device.attach(pid)
session =device.attach('cn.pojie52.cm01') #这里是要注入的apk包名
# 在目标进程里创建脚本
script = session.create_script(jscode)
# 注册消息回调
script.on('message', on_message)
print(' Start attach')
# 加载创建好的javascript脚本
script.load()
# 读取系统输入
sys.stdin.read()
var so_addr = Module.findBaseAddress("libnative-lib.so");
//查找so的基址。
console.log("so_addr:", so_addr);//打印so的基址
var addr_b90 = so_addr.add(0xb90);//so的基址加上b90就是这个函数的地址
if (so_addr) //判断基础是否获取到了
var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int','pointer']);
//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为_BYTE *password__, unsigned int password__length, char *threeStr
//返回值为unsigned __int64
//返回值对应'int',参数对应着['pointer', 'int','pointer']
下面为封装函数的参数,进行调用
//分配内存创建111111111111111111111111111111字符串
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
var arg2 = 30;//长度
//分配内存创建areyousure??????字符串
var arg3 = Memory.allocUtf8String('areyousure??????');
//调用sub_b90函数,传入arg1,arg2,arg3
var ret_b90 = sub_b90(arg1,arg2,arg3);
//返回值为ret_b90
Memory.readByteArray(arg1,64)//读取arg1内存中数据,也就是password,读64位byte的数据
console.log(Memory.readByteArray(arg1,64));//打印读取出来的password
var addr_d90 = so_addr.add(0xd90);//基址加上0xb90的偏移就是sub_d90函数的地址
var sub_d90 = new NativeFunction(addr_d90 , 'pointer', ['pointer', 'int' ]);
//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为char *a1, __int64 a2
//返回值为void * 万能指针,任何指针都可以赋值
//返回值对应'pointer',参数对应着['pointer', 'int']
//分配内存创建111111111111111111111111111111字符串
var arg1 = Memory.allocUtf8String('111111111111111111111111111111');
//密码长度
var arg2 = 30;
//调用sub_d90()函数,输入arg1,arg2参数
var ret_d90 = sub_d90(arg1,arg2);//返回值赋值给ret_d90
读取sub_d90返回值地址里面的空间的64位byte的数据
console.log(Memory.readByteArray(ret_d90,64));
public static void Xor(){
int xorData[]={0xe0,0x6b,0x37,0xa1,0x75,0xd7,0xf6,0xd4,0xef,0x19,0xc6,0xc3,0x57,0xa0,0xf9,0xb4,
0x73,0xee,0xc8,0xd1,0xb3,0x30,0x1a,0x0a,0x09,0x52,0x06,0x8c,0x1f,0x7c};
int xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};
System.out.print("[");
for (int i = 0; i < xorData.length; i++) {
System.out.print(xorData[i]^xorDataMy[i]);
if(i<xorData.length-1){
System.out.print(",");
}
}
System.out.print("]");
}
public static void baseEncode(){
System.out.println();
byte xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};
String by=Base64.getEncoder().encodeToString( xorDataMy);
System.out.println(by);
}
public static void md5(){
System.out.println(MD5Utils.stringToMD5("111111111111111111111111111111"));
}
while ( v9[v16] == v19[v16] )
{
if ( v9[v16] )
{
if ( ++v16 != 41 )
continue;
}
v17 = 1;
goto LABEL_9;
}
v17 = 0;
LABEL_9:
free(v9);
}
else
{
return 0;
}
return v17;
如何获取v19呢?
如下是v19的定义,地址为xsp+0
_BYTE v19[33]; // [xsp+0h] [xbp-A0h]
while ( v9[v16] == v19[v16] )
import frida, sys
jscode = '''
var destAddr = ''; //定位xsp地址
function inline_hook() {
var so_addr = Module.findBaseAddress("libnative-lib.so");//寻找基址
//在ida中基址为0000000000000
//在动态调试时基址会变
//所以我们需要通过基址加上函数的偏移来定位一个函数
if (so_addr) {//是否获取到基址,如果没有获取到里面的代码就不会执行
console.log("so_addr:", so_addr);//打印so的地址
var addr_b90 = so_addr.add(0xB90);//获取sub_b90的函数地址
var sub_b90 = new NativeFunction(addr_b90 , 'int', ['pointer', 'int', 'pointer']);
//创建一个本地函数,类似于指针函数,给指针函数赋值函数的地址。
//然后进行调用,参数为_BYTE *password__, unsigned int password__length, char *threeStr
//返回值为unsigned __int64
//返回值对应'int',参数对应着['pointer', 'int','pointer']
//下面是一个拦截器,用于拦截函数的sub_b90的进入,与结束
Interceptor.attach(sub_b90, {
onEnter: function(args) //进入函数时执行的代码
{
destAddr = args[0];//把第一个参数赋值给destAddr
console.log('onEnter B90'); //打印'onEnter B90,用于确认函数是否进入
},
//在进入函数之后执行的语句
onLeave:function(retval)
{
//retval为函数的返回值
console.log('onLeave B90');//打印onLeave B90,用于确认函数是否离开
}
});
var addr_b2c = so_addr.add(0xb2c);//0xb2c为偏移,也就是说拦截 b2c的地址
console.log("The addr_b2c:", addr_b2c);//打印addr_b2c的地址
Java.perform(function() {
Interceptor.attach(addr_b2c, {//拦截b2c地址
onEnter: function(args) { //arg为参数
console.log("addr_b2c OnEnter :", Memory.readByteArray(destAddr.sub(0x38),64) );
// Memory.readByteArray(destAddr.sub(0x38),64)//读取xsp的内容,也就v19的值
//也就是base64编码的真码
}
})
})
}
}
setImmediate(inline_hook)
'''
//用于接收消息的函数
//第一个参数为需要打印的信息
def on_message(message, data):
if message['type'] == 'send':
print(" {0}".format(message['payload']))
else:
print(message)
Pass//跳过
#print(frida.enumerate_devices())
# 查找USB设备并附加到目标进程
device = frida.get_remote_device()//调用frida的函数,获取设备
#pid = device.spawn(["com.live.xctv"])
#session = device.attach(pid)//
session =device.attach('cn.pojie52.cm01') #这里是要注入的apk包名
//这个包名可以通过frida-ps -U 来进行获取
# 在目标进程里创建脚本
script = session.create_script(jscode)
# 注册消息回调
script.on('message', on_message)
print(' Start attach')
# 加载创建好的javascript脚本
script.load()
# 读取系统输入
sys.stdin.read()
import base64//导入base64的包,因为我们要用到base64的解密函数
//xorkey是我们前面通过异或解密得出的数据
xorkey = [209, 90, 6, 144, 68, 230, 199, 229, 222, 40, 247, 242, 102, 145, 200, 133, 66, 223, 249, 224, 130, 1, 43, 59,
56, 99, 55, 189, 46, 77]
//第一个参数为base64解密后的数据
//第二个参数为base64解密后的数据的长度
def sub_B90(data, l):
ret = []
for i in range(l):
ret.append(data[i] ^ xorkey[i])//异或解密,把结果拼接到ret里面
s = ''
for i in ret://便利ret里面数据
s += chr(i) //把ret的解密结果转换成字符,如何进行拼接。
print(s)//打印拼接后的解密数据
return ret//返回ret
def resv(data):
data = base64.b64decode(data,)//解密base64数据
t = sub_B90(data, len(data))//把解密后的base64数据传入sub_b90进行异或解密
return (t)
data="5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8"//base64数据
resv(data)//调用这个函数进行解密base64数据与异或解密获得真吗
看雪ID:白云精灵
https://bbs.pediy.com/user-home-814281.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!