0x01 预备知识
Java基础
0x02 准备工具
AndroidKiller/ApkIDE..等等自己挑一个,我选AndroidKiller,我感觉界面比较舒服(其实并没有用到)
jadx-gui/Java Decompiler/jd-gui/jeb..等等自己挑一个,我还是用回了jadx-gui
idea/eclipse/android studio/ida..等动态调试工具,我选择idea,因为本身学java ee开发,一直都在用eclipse,但是idea出来后,一直想学习使用,但是没什么机会,这次学习移动安全,刚好有机会可以熟悉熟悉这个大家都说的最牛ide(最后还是用eclipse,而且没有用到动态调试,只是用来调试解码的代码)
ddms
0x03 望
我们把apk安装到模拟器里,看看是什么鬼
看到界面,大致就知道作用了,无非就是输入密码,登录看看对不对
这个在if逻辑层面CM很简单,改一改if语句就可以了,或者goto大法什么的,都行
但是这次是为了获取登录密码
0x04 闻
不管三七二十一,拖到jadx-gui里看看,这是入口MainActivity里的onCreate方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(1);
setContentView(R.layout.activity_main);
final EditText edit = (EditText) findViewById(R.id.edit);
((Button) findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String password = edit.getText().toString();
String table = MainActivity.this.getTableFromPic();
String pw = MainActivity.this.getPwdFromPic();
Log.i("lil", "table:" + table);
Log.i("lil", "pw:" + pw);
String enPassword = "";
try {
enPassword = MainActivity.bytesToAliSmsCode(table, password.getBytes("utf-8"));
Log.i("lil", "enPassword:" + enPassword);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (pw == null || pw.equals("") || !pw.equals(enPassword)) {
Builder builder = new Builder(MainActivity.this);
builder.setMessage(R.string.dialog_error_tips);
builder.setTitle(R.string.dialog_title);
builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
return;
}
MainActivity.this.showDialog();
}
});
}
0x05 问
代码还原的很好,几乎就是源代码了。
可以看到在按钮的onClick监听事件里面有三个变量password,table,pw
通过赋值可以判断password是你输入的值,其他两个变量是app自带的值,暂时先不管
String password = edit.getText().toString();
String table = MainActivity.this.getTableFromPic();
String pw = MainActivity.this.getPwdFromPic();
然后后面还有两个Log打印日志代码,分别把table,pw打印出来了
Log.i("lil", "table:" + table);
Log.i("lil", "pw:" + pw);
再往下看,在try-catch里,调用了个bytesToAliSmsCode方法,把table和输入的password进行了一系列的运算,赋值给了enPassword,而这一系列的运算,就是加密了
enPassword = MainActivity.bytesToAliSmsCode(table, password.getBytes("utf-8"));
再往下看,这个if语句进行了判断
如果pw是null值,或者pw是个空字符串,再或者pw和enPassword不一致,那么就会进入if里面
if (pw == null || pw.equals("") || !pw.equals(enPassword)) {
我们看看if里面的代码是干嘛的,在里面我们有发现一个语句
builder.setMessage(R.string.dialog_error_tips);
这个语句的作用大致就是设置消息内容为一个app的固定值dialog_error_tips,看名字应该知道是错误提示
然后在if最里面最后一行,有个return语句,代表这个事件结束了
那我们看到监听事件最后一行调用了
MainActivity.this.showDialog();
我们在jadx里安装ctrl点这个方法,就会跳到这个调用方法里
private void showDialog() {
Builder builder = new Builder(this);
builder.setMessage(R.string.dialog_good_tips);
builder.setTitle(R.string.dialog_title);
builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
瞄一眼这个代码,里面有个
builder.setMessage(R.string.dialog_good_tips);
那么从dialog_good_tips名字里就可以判断,这是一个好的提示
回过头再回顾一下
首先我们知道了这三个变量password,table,pw
但是我们只知道password是代表用户输入的密码
table是和password一起用作加密的字符串,加密后的字符串赋值给了enPassword
再根据if的判断语句我们知道
pw和enPassword进行比较
如果pw和enPassword不一样,就进入if语句,显示错误提示(dialog_error_tips),然后返回
如果pw和enPassword一样,就跳过if语句,显示好的提示(dialog_good_tips)
现在我们再看看enPassword是怎么加密的,回过头看一下加密代码,发现是调用了bytesToAliSmsCode方法
enPassword = MainActivity.bytesToAliSmsCode(table, password.getBytes("utf-8"));
同样在jadx里按住ctrl点这个方法,就会跳过去
private static String bytesToAliSmsCode(String table, byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(table.charAt(b & MotionEventCompat.ACTION_MASK));
}
return sb.toString();
}
其中data参数是password转换成了byte数组传了进去
然后for循环遍历data
然后把data的每一个byte与MotionEventCompat.ACTION_MASK进行与运算,假定运算结果为X
按住ctrl点击ACTION_MASK,可以跳到ACTION_MASK定义的地方,可以看到他的值是255
然后把运算的结果通过table的charAt方法获取到table里第X个字符,假定这个字符为C
然后把C加入sb变量的尾部,最后把sb转成String进行返回
既然if的判断里是判断pw是否与enPassword是否一样的,那么就可以知道pw也是这么加密过来的,所以我们只要解密pw就可以了
所以首先需要获取pw和table的值
在前面我们可以看到有log打印出pw和table的值,那么看来无需动态调试了,直接打开ddms,运行app就能看到pw和table的值了
0x06 切
把这两个值复制出来
03-02 15:35:27.758: I/lil(2633): table:一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐
03-02 15:35:27.758: I/lil(2633): pw:义弓么丸广之
然后打开idea或者eclipse,随便新建一个java项目,再建一个测试类
编写解密代码...
其实我真的就是编写了一个解密代码,但是我TM回过头发现,MainActivity里有那个解密方法- -
private static byte[] aliCodeToBytes(String codeTable, String strCmd) {
byte[] cmdBuffer = new byte[strCmd.length()];
for (int i = 0; i < strCmd.length(); i++) {
cmdBuffer[i] = (byte) codeTable.indexOf(strCmd.charAt(i));
}
return cmdBuffer;
}
这个方法是返回一个byte数组的,我们需要String
所以还是需要小改一下,下面就是改好的代码,直接返回字符串
private static String aliCodeToBytes(String codeTable, String strCmd) {
StringBuilder sb = new StringBuilder();
byte[] cmdBuffer = new byte[strCmd.length()];
for (int i = 0; i < strCmd.length(); i++) {
cmdBuffer[i] = (byte) codeTable.indexOf(strCmd.charAt(i));
sb.append((char)cmdBuffer[i]);
}
return sb.toString();
}
把table和pw传到这个方法里,然后打印,可以看到解密出来的密码是581026,
然后回到模拟器了试试看,OJBK!
APK实例下载