一道安卓逆向,由于没找到关键函数,比赛时没搞出来,复现一下
对APK进行反编译,结果十分诡异,看起来就像是对用户名进行了字符串反转,但测试后并非这样
public class MainActivity extends AppCompiatActivity {
class MyHandler extends Handler {
WeakReference mWeakReference;
public MyHandler(MainActivity arg2) {
super();
this.mWeakReference = new WeakReference(arg2);
}
public void handleMessage(Message arg6) {
String v6_1;
StringBuilder v1;
super.handleMessage(arg6);
Object v0 = this.mWeakReference.get();
if(v0 == null) {
return;
}
switch(arg6.what) {
case 0: {
Object v6 = arg6.obj;
if(!TextUtils.isEmpty(((CharSequence)v6))) {
v1 = new StringBuilder();
int v3;
for(v3 = 0; v3 < ((String)v6).length() / 2; ++v3) {
v1.append(((String)v6).charAt(v3));
}
v6_1 = v1.toString();
}
v1 = new StringBuilder();
v1.append("flag{");
v1.append(v6_1);
v1.append("}");
Toast.makeText(((Context)v0), v1.toString(), 1).show();
break;
}
case 1: {
Toast.makeText(((Context)v0), "登录失败", 1).show();
break;
}
default: {
break;
}
}
((MainActivity)v0).login.setEnabled(true);
}
}
private void login(String arg3, String arg4, Handler arg5) {
Toast.makeText(((Context)this), "登录中。。。", 1).show();
MainActivity.runnable = new Runnable(arg4, arg3, arg5) {
public void run() {
Message v0 = Message.obtain();
StringBuilder v1 = new StringBuilder(this.val$password);
if(this.val$name.equals(v1.reverse().toString())) {
v0.obj = v1.toString();
}
else {
v0.what = 1;
}
this.val$handler.sendMessage(v0);
}
};
MainActivity.cachedThreadPool.execute(MainActivity.runnable);
}
protected void onCreate(Bundle arg2) {
super.onCreate(arg2);
this.setContentView(2131296283);
this.login = this.findViewById(2131165260);
this.handler = new MyHandler(this);
this.login.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg4) {
MainActivity.this.mName = MainActivity.this.name.getText().toString();
MainActivity.this.mPassword = MainActivity.this.password.getText().toString();
if(!TextUtils.isEmpty(MainActivity.this.mName)) {
if(TextUtils.isEmpty(MainActivity.this.mPassword)) {
}
else {
MainActivity.hasLogin = true;
MainActivity.this.login.setEnabled(false);
MainActivity.this.login(MainActivity.this.mName, MainActivity.this.mPassword, MainActivity.this.handler);
return;
}
}
Toast.makeText(MainActivity.this, "用户名或密码为空", 1).show();
}
});
this.name = this.findViewById(2131165265);
this.password = this.findViewById(2131165277);
}
}
所以直接考虑在native层动了手脚(然而也可能并不是,因为这里功能本质上都由MainActivity的父类AppCompiatApp来完成,并在完成功能后将button设置为不可点击状态,这使得MainActivity中的功能无法使用。谁特喵想得到它把这个东西换了呢orz),来到JNI_LOAD函数中查看
signed int __fastcall JNI_OnLoad(_JNIEnv *a1)
{
int v1; // r8
signed int result; // r0
_JNIEnv *env_; // r5
int v4; // r6
_JNIEnv *v5; // [sp+0h] [bp-18h]
int v6; // [sp+4h] [bp-14h]
int v7; // [sp+8h] [bp-10h]
v7 = v1;
v5 = 0;
if ( !((int (*)(void))a1->functions->FindClass)() )
goto LABEL_4;
LABEL_2:
result = -1;
while ( _stack_chk_guard != v6 )
{
LABEL_4:
env_ = v5;
v4 = ((int (__fastcall *)(_JNIEnv *, char (*)[42]))v5->functions->FindClass)(v5, off_51504010);// android/support/v7/app/AppCompiatActivity
dword_51504110 = ((int (__fastcall *)(_JNIEnv *, int))env_->functions->NewGlobalRef)(env_, v4);
if ( !v4
|| ((int (__fastcall *)(_JNIEnv *, int, char **, signed int))env_->functions->RegisterNatives)(env_, v4, aEq_, 1) <= -1 )
{
goto LABEL_2;
}
result = 0x10006;
}
return result;
}
可以看到这里在android/support/v7/app/AppCompiatActivity活动中注册了Eq函数,去到AppCompiatActivity中查看
protected native boolean eq(String arg1) {
}
protected void onStart() {
super.onStart();
this.login = this.findViewById(2131165260);
this.login.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg5) {
AppCompiatActivity.this.mName = AppCompiatActivity.this.name.getText().toString();
AppCompiatActivity.this.mPassword = AppCompiatActivity.this.password.getText().toString();
if(!TextUtils.isEmpty(AppCompiatActivity.this.mName)) {
if(TextUtils.isEmpty(AppCompiatActivity.this.mPassword)) {
}
else {
int v1 = 0;
AppCompiatActivity.this.login.setEnabled(false);
if(AppCompiatActivity.this.eq(AppCompiatActivity.this.mPassword)) {
byte[] v5 = AppCompiatActivity.this.mPassword.getBytes();
int v3 = 24;
if(v5.length != v3) {
byte[] v2 = new byte[v3];
while(v1 < v2.length) {
byte v3_1 = v1 < v5.length ? v5[v1] : ((byte)v1);
v2[v1] = v3_1;
++v1;
}
v5 = v2;
}
v5 = AppCompiatActivity.dec(v5, "2ggdrsLgM7iPNYPQrD58Rg==".getBytes());
AppCompiatActivity v1_1 = AppCompiatActivity.this;
StringBuilder v2_1 = new StringBuilder();
v2_1.append("flag{");
v2_1.append(new String(v5));
v2_1.append("}");
Toast.makeText(((Context)v1_1), v2_1.toString(), 1).show();
}
else {
Toast.makeText(AppCompiatActivity.this, "error", 1).show();
}
return;
}
}
Toast.makeText(AppCompiatActivity.this, "用户名或密码为空", 1).show();
}
});
this.name = this.findViewById(2131165265);
this.name.setEnabled(false);
this.password = this.findViewById(2131165277);
}
可以看到这里使用我们的输入作为AES密钥去解密一串base64来得到flag,而输入的验证则在Eq函数中,Eq函数在JNI_LOAD中注册,在native层可以找到Eq函数的代码如下
int __fastcall sub_51500784(_JNIEnv *a1)
{
size_t lenth; // r10
unsigned __int8 *buffer0; // r6
_BYTE *buffer1; // r8
_BYTE *buffer2; // r11
signed int i; // r0
size_t lenth_; // r2
char *v7; // r1
int c; // r3
int i_; // r1
unsigned int v10; // r2
int v11; // r3
int v12; // r0
int v13; // r4
unsigned __int8 v14; // r0
_BYTE *buffer2_; // r3
char *buffer0_c; // r5
char *v17; // r4
int j; // r5
int v19; // r1
int v20; // r0
signed int v21; // r1
int v22; // r2
size_t len_input; // r0
unsigned int len_input_; // r8
unsigned int v25; // r5
_BYTE *v26; // r0
int v27; // r3
int n; // r10
unsigned int index; // r2
int v30; // r12
bool v31; // zf
_BYTE *v32; // r1
bool v33; // zf
int v34; // r3
int v35; // r1
unsigned __int8 v36; // r11
unsigned int v37; // lr
char v38; // r1
const char *v39; // r2
int v40; // t1
unsigned int v42; // [sp+4h] [bp-234h]
unsigned int v43; // [sp+8h] [bp-230h]
unsigned int v44; // [sp+10h] [bp-228h]
char *input; // [sp+14h] [bp-224h]
char box0[256]; // [sp+18h] [bp-220h]
char buffer3[256]; // [sp+118h] [bp-120h]
int v48; // [sp+218h] [bp-20h]
input = (char *)((int (*)(void))a1->functions->GetStringUTFChars)();
lenth = strlen(a650f909c721736);
buffer0 = (unsigned __int8 *)malloc(lenth);
buffer1 = malloc(lenth);
buffer2 = malloc(lenth);
_aeabi_memclr((int)buffer0, lenth); // memset(dest, 0, n);
_aeabi_memclr((int)buffer1, lenth);
_aeabi_memclr((int)buffer2, lenth);
if ( lenth )
{
i = 0;
lenth_ = lenth;
v7 = a650f909c721736;
do
{
c = (unsigned __int8)*v7++;
if ( c != '-' )
buffer1[i++] = c;
--lenth_;
}
while ( lenth_ ); // clear '-'
if ( i >= 1 )
{
i_ = i - 1;
v10 = -8;
v11 = 0;
v12 = 0;
do
{
if ( (v11 | (v10 >> 2)) > 3 )
{
v13 = v12;
}
else
{
v13 = v12 + 1;
buffer0[v12] = 45;
}
v14 = buffer1[i_--];
v11 += 0x40000000;
buffer0[v13] = v14;
++v10;
v12 = v13 + 1;
}
while ( i_ != -1 );
if ( v13 >= 0 )
{
buffer2_ = buffer2;
while ( 1 )
{
buffer0_c = (char *)*buffer0;
if ( (unsigned __int8)((_BYTE)buffer0_c - 'a') <= 5u )
break;
if ( (unsigned __int8)((_BYTE)buffer0_c - '0') <= 9u )
{
buffer0_c = &aDbeafc24097158[(_DWORD)buffer0_c - '*'];
goto LABEL_18;
}
LABEL_19:
*buffer2_++ = (_BYTE)buffer0_c;
--v12;
++buffer0;
if ( !v12 )
goto LABEL_20;
}
buffer0_c = &aDbeafc24097158[(_DWORD)buffer0_c - 'a'];
LABEL_18:
LOBYTE(buffer0_c) = *buffer0_c;
goto LABEL_19;
}
}
}
LABEL_20:
_aeabi_memcpy8(box0, &unk_515023E8, 256);
v17 = buffer3;
j = 0;
do
{
sub_51500D20(j, lenth);
buffer3[j++] = buffer2[v19];
}
while ( j != 256 );
v20 = (unsigned __int8)(buffer3[0] - 41);
box0[0] = box0[v20];
box0[v20] = -41;
v21 = 1;
do
{
v22 = (unsigned __int8)box0[v21];
v20 = (v20 + (unsigned __int8)buffer3[v21] + v22) % 256;
box0[v21++] = box0[v20];
box0[v20] = v22;
}
while ( v21 != 256 );
len_input = strlen(input); // 以下check()
len_input_ = len_input;
v25 = (unsigned __int8)buffer2[3];
v43 = 8 * (3 - -3 * (len_input / 3));
v42 = v25 + v43 / 6;
v26 = malloc(v42 + 1);
if ( len_input_ )
{
n = 0;
index = 0;
v30 = 0;
v44 = v25;
do
{
n = (n + 1) % 256;
v35 = (unsigned __int8)box0[n];
v30 = (v30 + v35) % 256;
box0[n] = box0[v30];
box0[v30] = v35;
v17 = (char *)(unsigned __int8)box0[n];
v36 = box0[(unsigned __int8)(v35 + (_BYTE)v17)] ^ input[index];
if ( index && (v27 = 0xAAAAAAAB * (unsigned __int64)index >> 32, v37 = 3 * (index / 3), v37 != index) )// index不为3的倍数
{
v31 = index == 1;
if ( index != 1 )
v31 = v37 + 1 == index;
if ( v31 )
{
v32 = aAbcdefghijklmn;
v26[v44 + index] = aAbcdefghijklmn[(unsigned __int8)v26[v44 + index] | ((unsigned int)v36 >> 4)];
v17 = &v26[v44 + index];
v27 = 4 * v36 & 0x3C;
v17[1] = v27;
if ( index + 1 >= len_input_ )
goto LABEL_53;
}
else
{
v33 = index == 2;
if ( index != 2 )
v33 = v37 + 2 == index;
if ( v33 )
{
v17 = (char *)(v36 & 0xC0);
v34 = v44++ + index;
v26[v34] = aAbcdefghijklmn[(unsigned __int8)v26[v34] | ((unsigned int)v17 >> 6)] ^ 0xF;
v27 = (int)&v26[v34];
*(_BYTE *)(v27 + 1) = aAbcdefghijklmn[v36 & 0x3F];
}
}
}
else
{
v26[v44 + index] = aAbcdefghijklmn[(unsigned int)v36 >> 2] ^ 7;
v17 = &v26[v44 + index];
v27 = 16 * v36 & 0x30;
v17[1] = v27;
if ( index + 1 >= len_input_ )
{
v38 = aAbcdefghijklmn[v27];
*((_WORD *)v17 + 1) = 15163;
goto LABEL_43;
}
}
++index;
}
while ( index < len_input_ );
}
while ( 1 )
{
if ( v43 )
{
v32 = (_BYTE *)1;
v17 = (char *)v42;
v39 = " {9*8ga*l!Tn?@#fj'j$\\g;;";
do
{
v27 = (unsigned __int8)v26[v25++];
v40 = *(unsigned __int8 *)v39++;
if ( v40 != v27 )
v32 = 0;
}
while ( v25 < v42 );
}
else
{
v32 = (_BYTE *)1;
}
v26 = (_BYTE *)(_stack_chk_guard - v48);
if ( _stack_chk_guard == v48 )
break;
LABEL_53:
v38 = v32[v27];
v17[2] = 52;
LABEL_43:
v17[1] = v38;
}
return (unsigned __int8)v32;
}
关于这段代码,主要是rc4的魔改和base64的魔改,看了大佬们的WP,大概有3种解法。
1.根据加密算法逆出解密算法
常规的做法,不过得花一定时间
2.穷举输入
v36 = sbox[(unsigned __int8)(v35 + (_BYTE)v17)] ^ input[index];
由于最终的验证就是魔改的base64对v36的数据流进行编码来得到密文,且密文已知,这里又是逐字节异或,所以我们可以不逆魔改的RC4,通过动态调试得到生成的sbox,然后copy这段RC4加密进行穷举。
3.手动解密
v36 = sbox[(unsigned __int8)(v35 + (_BYTE)v17)] ^ input[index];
还是这行代码,由于密文已知,sbox已知,这里使用的是异或加密,所以本质上如果将我们的输入改为密文,则v36的数据流能直接得到最终的key,但由于这里v36作为字节在异或后立即进行了魔改的base64,所以在最终验证时得到的是NewBase64(key),所以还需要进行一次解NewBase64即可得到key。