-
-
[原创]提交解题思路(enimey)
-
发表于: 2015-1-25 17:16 2153
-
第一题:
使用JEB反编译APK,关键代码为:
String v3 = this.val$edit.getText().toString();
String v5 = MainActivity.this.getTableFromPic();
String v4 = MainActivity.this.getPwdFromPic();
Log.i("lil", "table:" + v5);
Log.i("lil", "pw:" + v4);
try {
String v2 = MainActivity.bytesToAliSmsCode(v5, v3.getBytes("utf-8"));
Log.i("lil", "enPassword:" + v2);
}
catch(UnsupportedEncodingException v1) {
v1.printStackTrace();
}
if(v4 == null || (v4.equals("")) || !v4.equals(v2)) {
AlertDialog$Builder v0 = new AlertDialog$Builder(MainActivity.this);
v0.setMessage(2131361809);
v0.setTitle(2131361808);
v0.setPositiveButton(2131361811, new DialogInterface$OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
v0.show();
}
实际上就是一个简单的查表验证,通过Log可以直接得到table和pw,反向查找pw在table中的序号,正确的输入为:581026;
第二题:
JEB反编译APK,验证代码为:
if(MainActivity.this.securityCheck(MainActivity.this.inputCode.getText().toString())
) {
MainActivity.this.startActivity(new Intent(MainActivity.this, ResultActivity.class));
}
else {
Toast.makeText(MainActivity.this.getApplicationContext(), "验证码校验失败", 0).show();
}
securityCheck为native函数,即验证逻辑在SO中完成。
SO有反调试,首先过掉反调试,步骤为:
1)准备好调试环境(ida、adb等)
1) adb shell
2)am start -D -n com.yaotong.crackme/.MainActivity
3) 使用IDA附加上该APK进程,设置加载SO时端下来,在dlopen下断点
4)jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
5)continue
6)在libcrackme.so的JNI_OnLoad下断点
7)单步跟踪结合IDA静态分析和F5,并在fgets处下断点,SO是通过读取/proc/pid/status文件中的Tracepid字段来判断是否有调试,如果检测到调试程序将会退出。最终,我才用patch跳转指令过掉了反调试:
BLX R11 ; 调sscanf函数
LDR R0, [SP,#0x348+var_310]
CMP R0, #1
BGE loc_75025600 ; 把这个跳转取消掉就可以了
8)在Java_com_yaotong_crackme_MainActivity_securityCheck下断点,continue
9)通过动态调试,验证逻辑为将用户的输入与字符串“aiyou,bucuoo”进行比较,匹配则成功。
最终正确的输入为:aiyou,bucuoo
第三题:
1)使用JDB反编译APK,发现只有一个StubApplication类,该类的功能就是加载SO。初步判断是采用了Application代理对dex进行了加壳。
2)进一步分析,发现在lib/armeabi/目录中,libmobisecy.so实际上是一个zip文件,解压后得到一个classes.dex文件,拖到JEB里,发现除了方法实体全被替换为了throw new RuntimeException();即我们只需要修复codeOff即可完成dex脱壳。
3)使用IDA动态调试SO,SO做了混淆,并且通过调用ptrace和检测/proc/pid/status检测调试器,动态调了半天,坑太大放弃了。
4)换了种思路,当APK正常运行后,其codeOff必定是已经恢复过后的,那么我们直接在APK正常运行后,得到codeOff,然后修复原dex即可。
5)由于这种加壳方式,最后必定是要通过调用Application.onCreate来启动真正的逻辑的。也就是说当执行Application.onCreate时,codeOff已经修正完毕。
6)我的最终解决办法为修改Android源码,定制一个系统,从而得到real_codeOff。
7)在dalvik/vm/interp/Stack.cpp文件的dvmCallMethodV函数,添加自己的逻辑:
判断函数名是否为onCreate->获得DexFile结构体->解析DexFile循环打印出所有codeOff
8)通过上步得到的codeOff修复原dex
9)使用JDB反编译修复过后的dex,验证逻辑为:
public void run() {
int v0_6;
String v0_5;
String v1_5;
byte[] v2_3;
Cipher v1_1;
MessageDigest v0_3;
String v5;
dn.b(dn.a());
MessageDigest v1 = null;
if(Build$VERSION.SDK_INT >= 10 && (Debug.isDebuggerConnected())) {
this.b.sendEmptyMessage(1);
return;
}
try {
v5 = new e().a(this.c);
}
catch(Exception v0) {
this.b.sendEmptyMessage(3);
return;
}
try {
if(v5.equals("sos")) {
this.b.sendEmptyMessage(2);
return;
}
CRC32 v0_1 = new CRC32();
v0_1.update(v5.getBytes());
v0_1.getValue();
v5.hashCode();
try {
v0_3 = MessageDigest.getInstance("sha1");
}
catch(NoSuchAlgorithmException v0_2) {
v0_2.printStackTrace();
v0_3 = v1;
}
try {
v1_1 = Cipher.getInstance("AES");
}
catch(NoSuchPaddingException v0_4) {
v0_4.printStackTrace();
return;
}
catch(NoSuchAlgorithmException v2) {
v2.printStackTrace();
}
if(!b.a && v1_1 == null) {
throw new AssertionError();
}
}
catch(Exception v0) {
goto label_25;
}
int v2_1 = 2;
try {
v1_1.init(v2_1, new SecretKeySpec(Base64.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes(), 0),
"AES"));
goto label_65;
}
catch(Exception v0) {
}
catch(InvalidKeyException v2_2) {
label_65:
try {
new byte[0];
try {
v2_3 = v1_1.doFinal(Base64.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8"),
0));
goto label_74;
}
catch(UnsupportedEncodingException v1_2) {
try {
v1_2.printStackTrace();
label_74:
String v6 = new String(v2_3);
v0_3.update(new byte[]{127});
v0_3.update(v5.getBytes());
v0_3.update(new byte[]{1});
}
catch(Exception v0) {
goto label_25;
}
}
catch(BadPaddingException v1_3) {
goto label_74;
}
catch(IllegalBlockSizeException v1_4) {
goto label_74;
}
}
catch(Exception v0) {
goto label_25;
}
try {
v1_5 = new String(Base64.encode(v0_3.digest(), 0));
goto label_93;
}
catch(Exception v0) {
try {
v0.printStackTrace();
return;
label_93:
if(!v5.equals(v6)) {
goto label_183;
}
else if(Arrays.equals(v1_5.getBytes(), "2398lj2n".getBytes())) {
this.b.sendEmptyMessage(0);
return;
}
else {
v0_5 = "234";
}
goto label_111;
}
catch(Exception v0) {
goto label_25;
}
}
label_183:
v0_5 = v1_5;
try {
label_111:
if(v0_5.equals("lsdf==")) {
this.b.sendEmptyMessage(0);
return;
}
char[] v1_6 = v5.toCharArray();
v0_6 = v5.substring(0, 2).hashCode();
if(v0_6 > 3904) {
this.b.sendEmptyMessage(4);
return;
}
if(v0_6 == 3618) {
if(v1_6[0] + v1_6[1] != 168) {
goto label_171;
}
do {
label_138:
byte[] v5_1 = e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)
.a().getBytes();
if(v1_6.length - 2 == v5_1.length) {
v0_6 = 0;
while(true) {
if(v0_6 >= v5_1.length) {
goto label_181;
}
else if(v1_6[v0_6 + 2] != v5_1[v0_6]) {
v0_6 = 0;
}
else {
++v0_6;
continue;
}
goto label_163;
}
}
goto label_170;
}
while(true);
}
goto label_171;
}
catch(Exception v0) {
goto label_25;
}
label_181:
v0_6 = 1;
try {
label_163:
if(v0_6 != 0) {
this.b.sendEmptyMessage(0);
return;
}
label_170:
if(v2_3 == null) {
goto label_138;
}
label_171:
this.b.sendEmptyMessage(1);
}
catch(Exception v0) {
label_25:
this.b.sendEmptyMessage(1);
}
}
}
当输入正确时,跳转到this.b.sendEmptyMessage(0)
验证逻辑:
1) 将用户输入作为map的key,并以空格分开,得到对应的value
2)value.substring(0, 2).hashCode需等于3618
3)value[0] + value[1] 需等于168
4)通过前面两个条件可以确定输入的前两个key:...和_____
5)value的后面的数据要和e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)相同
6)同样的思路直接在dalvik/vm/reflect/Annotation.cpp的processAnnotationValue函数处添加自己的逻辑:
1)首先在开始打印出className:ALOGD("***processAnnotationValue*** className: %s", clazz->descriptor);
2)在case kDexAnnotationString处打印出值:ALOGD("elemObj: %s", dvmCreateCstrFromString((const StringObject *)elemObj));
7)得到后面的value为7e1p,则对应的key为:____. . ..___ .__.
8)最终,正确的输入为:... _____ ____. . ..___ .__.
第四题:
和第三题同样的方法修复了dex,嗯。。未完待续
使用JEB反编译APK,关键代码为:
String v3 = this.val$edit.getText().toString();
String v5 = MainActivity.this.getTableFromPic();
String v4 = MainActivity.this.getPwdFromPic();
Log.i("lil", "table:" + v5);
Log.i("lil", "pw:" + v4);
try {
String v2 = MainActivity.bytesToAliSmsCode(v5, v3.getBytes("utf-8"));
Log.i("lil", "enPassword:" + v2);
}
catch(UnsupportedEncodingException v1) {
v1.printStackTrace();
}
if(v4 == null || (v4.equals("")) || !v4.equals(v2)) {
AlertDialog$Builder v0 = new AlertDialog$Builder(MainActivity.this);
v0.setMessage(2131361809);
v0.setTitle(2131361808);
v0.setPositiveButton(2131361811, new DialogInterface$OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
v0.show();
}
实际上就是一个简单的查表验证,通过Log可以直接得到table和pw,反向查找pw在table中的序号,正确的输入为:581026;
第二题:
JEB反编译APK,验证代码为:
if(MainActivity.this.securityCheck(MainActivity.this.inputCode.getText().toString())
) {
MainActivity.this.startActivity(new Intent(MainActivity.this, ResultActivity.class));
}
else {
Toast.makeText(MainActivity.this.getApplicationContext(), "验证码校验失败", 0).show();
}
securityCheck为native函数,即验证逻辑在SO中完成。
SO有反调试,首先过掉反调试,步骤为:
1)准备好调试环境(ida、adb等)
1) adb shell
2)am start -D -n com.yaotong.crackme/.MainActivity
3) 使用IDA附加上该APK进程,设置加载SO时端下来,在dlopen下断点
4)jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
5)continue
6)在libcrackme.so的JNI_OnLoad下断点
7)单步跟踪结合IDA静态分析和F5,并在fgets处下断点,SO是通过读取/proc/pid/status文件中的Tracepid字段来判断是否有调试,如果检测到调试程序将会退出。最终,我才用patch跳转指令过掉了反调试:
BLX R11 ; 调sscanf函数
LDR R0, [SP,#0x348+var_310]
CMP R0, #1
BGE loc_75025600 ; 把这个跳转取消掉就可以了
8)在Java_com_yaotong_crackme_MainActivity_securityCheck下断点,continue
9)通过动态调试,验证逻辑为将用户的输入与字符串“aiyou,bucuoo”进行比较,匹配则成功。
最终正确的输入为:aiyou,bucuoo
第三题:
1)使用JDB反编译APK,发现只有一个StubApplication类,该类的功能就是加载SO。初步判断是采用了Application代理对dex进行了加壳。
2)进一步分析,发现在lib/armeabi/目录中,libmobisecy.so实际上是一个zip文件,解压后得到一个classes.dex文件,拖到JEB里,发现除了方法实体全被替换为了throw new RuntimeException();即我们只需要修复codeOff即可完成dex脱壳。
3)使用IDA动态调试SO,SO做了混淆,并且通过调用ptrace和检测/proc/pid/status检测调试器,动态调了半天,坑太大放弃了。
4)换了种思路,当APK正常运行后,其codeOff必定是已经恢复过后的,那么我们直接在APK正常运行后,得到codeOff,然后修复原dex即可。
5)由于这种加壳方式,最后必定是要通过调用Application.onCreate来启动真正的逻辑的。也就是说当执行Application.onCreate时,codeOff已经修正完毕。
6)我的最终解决办法为修改Android源码,定制一个系统,从而得到real_codeOff。
7)在dalvik/vm/interp/Stack.cpp文件的dvmCallMethodV函数,添加自己的逻辑:
判断函数名是否为onCreate->获得DexFile结构体->解析DexFile循环打印出所有codeOff
8)通过上步得到的codeOff修复原dex
9)使用JDB反编译修复过后的dex,验证逻辑为:
public void run() {
int v0_6;
String v0_5;
String v1_5;
byte[] v2_3;
Cipher v1_1;
MessageDigest v0_3;
String v5;
dn.b(dn.a());
MessageDigest v1 = null;
if(Build$VERSION.SDK_INT >= 10 && (Debug.isDebuggerConnected())) {
this.b.sendEmptyMessage(1);
return;
}
try {
v5 = new e().a(this.c);
}
catch(Exception v0) {
this.b.sendEmptyMessage(3);
return;
}
try {
if(v5.equals("sos")) {
this.b.sendEmptyMessage(2);
return;
}
CRC32 v0_1 = new CRC32();
v0_1.update(v5.getBytes());
v0_1.getValue();
v5.hashCode();
try {
v0_3 = MessageDigest.getInstance("sha1");
}
catch(NoSuchAlgorithmException v0_2) {
v0_2.printStackTrace();
v0_3 = v1;
}
try {
v1_1 = Cipher.getInstance("AES");
}
catch(NoSuchPaddingException v0_4) {
v0_4.printStackTrace();
return;
}
catch(NoSuchAlgorithmException v2) {
v2.printStackTrace();
}
if(!b.a && v1_1 == null) {
throw new AssertionError();
}
}
catch(Exception v0) {
goto label_25;
}
int v2_1 = 2;
try {
v1_1.init(v2_1, new SecretKeySpec(Base64.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes(), 0),
"AES"));
goto label_65;
}
catch(Exception v0) {
}
catch(InvalidKeyException v2_2) {
label_65:
try {
new byte[0];
try {
v2_3 = v1_1.doFinal(Base64.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8"),
0));
goto label_74;
}
catch(UnsupportedEncodingException v1_2) {
try {
v1_2.printStackTrace();
label_74:
String v6 = new String(v2_3);
v0_3.update(new byte[]{127});
v0_3.update(v5.getBytes());
v0_3.update(new byte[]{1});
}
catch(Exception v0) {
goto label_25;
}
}
catch(BadPaddingException v1_3) {
goto label_74;
}
catch(IllegalBlockSizeException v1_4) {
goto label_74;
}
}
catch(Exception v0) {
goto label_25;
}
try {
v1_5 = new String(Base64.encode(v0_3.digest(), 0));
goto label_93;
}
catch(Exception v0) {
try {
v0.printStackTrace();
return;
label_93:
if(!v5.equals(v6)) {
goto label_183;
}
else if(Arrays.equals(v1_5.getBytes(), "2398lj2n".getBytes())) {
this.b.sendEmptyMessage(0);
return;
}
else {
v0_5 = "234";
}
goto label_111;
}
catch(Exception v0) {
goto label_25;
}
}
label_183:
v0_5 = v1_5;
try {
label_111:
if(v0_5.equals("lsdf==")) {
this.b.sendEmptyMessage(0);
return;
}
char[] v1_6 = v5.toCharArray();
v0_6 = v5.substring(0, 2).hashCode();
if(v0_6 > 3904) {
this.b.sendEmptyMessage(4);
return;
}
if(v0_6 == 3618) {
if(v1_6[0] + v1_6[1] != 168) {
goto label_171;
}
do {
label_138:
byte[] v5_1 = e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)
.a().getBytes();
if(v1_6.length - 2 == v5_1.length) {
v0_6 = 0;
while(true) {
if(v0_6 >= v5_1.length) {
goto label_181;
}
else if(v1_6[v0_6 + 2] != v5_1[v0_6]) {
v0_6 = 0;
}
else {
++v0_6;
continue;
}
goto label_163;
}
}
goto label_170;
}
while(true);
}
goto label_171;
}
catch(Exception v0) {
goto label_25;
}
label_181:
v0_6 = 1;
try {
label_163:
if(v0_6 != 0) {
this.b.sendEmptyMessage(0);
return;
}
label_170:
if(v2_3 == null) {
goto label_138;
}
label_171:
this.b.sendEmptyMessage(1);
}
catch(Exception v0) {
label_25:
this.b.sendEmptyMessage(1);
}
}
}
当输入正确时,跳转到this.b.sendEmptyMessage(0)
验证逻辑:
1) 将用户输入作为map的key,并以空格分开,得到对应的value
2)value.substring(0, 2).hashCode需等于3618
3)value[0] + value[1] 需等于168
4)通过前面两个条件可以确定输入的前两个key:...和_____
5)value的后面的数据要和e.class.getAnnotation(f.class).a() + a.class.getAnnotation(f.class)相同
6)同样的思路直接在dalvik/vm/reflect/Annotation.cpp的processAnnotationValue函数处添加自己的逻辑:
1)首先在开始打印出className:ALOGD("***processAnnotationValue*** className: %s", clazz->descriptor);
2)在case kDexAnnotationString处打印出值:ALOGD("elemObj: %s", dvmCreateCstrFromString((const StringObject *)elemObj));
7)得到后面的value为7e1p,则对应的key为:____. . ..___ .__.
8)最终,正确的输入为:... _____ ____. . ..___ .__.
第四题:
和第三题同样的方法修复了dex,嗯。。未完待续
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
他的文章
看原图
赞赏
雪币:
留言: