钉钉CTF+AliCTF2015第三题+Android逆向小助手使用详解
本帖最后由 Anonymous、 于 2018-10-12 20:39 编辑一、说明
钉钉CTF是一个网友昨晚给我的,比较简单,但是思路和AliCTF非常像,所以就放在一起了,一些坛友问工具怎么使用
其实很简单,在这也稍微加下使用说明,自己技术比较菜,全程靠猜~~~~~有什么不对的地方,还请各位师傅指点.
二、钉钉CTF
这一题比较简单,先打开app看下
随便输入
错了,但是有个welcome to flag blank!根据题目意思,尝试什么都不输入
什么??????居然就对了!!!!!
好了,开始看Ali的吧...............
算啦算啦,还是看看内部怎么实现的这么牛逼的功能的吧
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(2130968603);
this.text = this.findViewById(2131427416);
this.textView1 = this.findViewById(2131427418);
this.button = this.findViewById(2131427415);
this.button.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg4) {
MainActivity.this.c = new CheckClass();
MainActivity.this.c.a(MainActivity.this.text.getText().toString());//先获取输入的字符串
if(MainActivity.this.c.check()) {//关键点
MainActivity.this.textView1.setText("flag is XMAN{" + MainActivity.this.text.getText().toString() + "}");
}
else {
MainActivity.this.textView1.setText("WORING!");
}
}
});
}
onCreate方法中,先看MainActivity.this.c.a
public void a(String arg5) {
int v1 = 30;
this.A = new byte;
this.B = arg5.getBytes();
int v0;
for(v0 = 0; v0 < arg5.length(); ++v0) {
this.A = this.B;
}
this.B = new byte;
}
获取输入的字符串,再看MainActivity.this.c.check()
public boolean check() {
boolean v9 = false;
int[] v0 = new int[]{40, 42, 65, 67, 68, 2, 64, 70, 96, 98, 181, 7, 10, 64, 23, 17, 37, 20, 45, 91, 74, 72, 135, 33, 57, 43, 87, 99, 147, 53};
byte[] v5 = new byte[]{52, 111, 102, 113, 52, 52, 98};
int v2 = 0;
int v4 = 0;
int v7 = 0;
int v1;
for(v1 = 0; v1 < v0.length; ++v1) {
int v8 = this.b(v0);
new String();
Log.d("now array:", String.valueOf(v8));
switch(v8) {
case 0: {
this.A = ((byte)(this.A ^ v7));
break;
}
case 1: {
if(this.A != 0) {
++v4;
}
else {
}
break;
}
case 2: {
v5 = ((byte)(v5 ^ v4));
++v4;
break;
}
case 3: {
if(v5 == this.A) {
++v7;
}
else {
}
break;
}
case 4: {
if(v7 == v4) {
v9 = true;
}
return v9;
}
case 5: {
if(v4 != v5.length) {
v1 = v0.length - 3;
}
else {
v4 = 0;
}
break;
}
default: {
++v2;
break;
}
}
}
return v9;
}
public int b(int arg4) {
int v0 = 181 & arg4;
return (v0 & 1) + ((v0 & 4) >> 2) + ((v0 & 16) >> 4) + ((v0 & 32) >> 5) + ((v0 & 128) >> 7);
}
此函数为关键点,看着挺复杂的,只要返回为true即可
case 4: {
if(v7 == v4) {
v9 = true;
}
return v9;
}
case 4里的判断执行,下面再接着分析v8 int v8 = this.b(v0);
由于v0是固定的,所以v8也是固定的,那么switch语句就好分析了
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class DCtf {
private static byte[] A;
private static byte[] B;
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
a("4");
check();
}
public static void a(String arg5) {
int v1 = 30;
A = new byte;
B = arg5.getBytes();
int v0;
for(v0 = 0; v0 < arg5.length(); ++v0) {
A = B;
}
B = new byte;
}
public static int b(int arg4) {
int v0 = 181 & arg4;
return (v0 & 1) + ((v0 & 4) >> 2) + ((v0 & 16) >> 4) + ((v0 & 32) >> 5) + ((v0 & 128) >> 7);
}
public static boolean check() {
boolean v9 = false;
int[] v0 = new int[]{40, 42, 65, 67, 68, 2, 64, 70, 96, 98, 181, 7, 10, 64, 23, 17, 37, 20, 45, 91, 74, 72, 135, 33, 57, 43, 87, 99, 147, 53};
byte[] v5 = new byte[]{52, 111, 102, 113, 52, 52, 98};
int v2 = 0;
int v4 = 0;
int v7 = 0;
int v1;
for(v1 = 0; v1 < v0.length; ++v1) {
int v8 = b(v0);
new String();
System.out.println("now array:" + String.valueOf(v8));
switch(v8) {
case 0: {
A = ((byte)(A ^ v7));
break;
}
case 1: {
if(A != 0) {
++v4;
}
else {
}
break;
}
case 2: {
v5 = ((byte)(v5 ^ v4));
++v4;
break;
}
case 3: {
if(v5 == A) {
System.out.println("v5 "+v5+"------A "+A+" "+v7);
++v7;
}
else {
}
break;
}
case 4: {
if(v7 == v4) {
v9 = true;
}
System.out.println("v7 "+v7+"------v4 "+v4);
return v9;
}
case 5: {
if(v4 != v5.length) {
v1 = v0.length - 3;
}
else {
v4 = 0;
}
break;
}
default: {
++v2;
break;
}
}
}
return v9;
}
}
运行上面的代码,可以得到v8的值分别是
now array:1
now array:1
now array:1
now array:1
now array:1
now array:0
now array:0
now array:1
now array:1
now array:1
now array:5
now array:3
now array:4
最后执行的就是case 4,符合我们前面的分析,但是v7 == v4,能影响v7的只有case 3这个分支,看上面v8的值,知道下面的代码只执行了一次
case 3: {
if(v5 == this.A) {
++v7;
}
else {
}
break;
}
所以要想v7 == v4,v7和v4只能为1或者0,0就是什么都不输入,++v7执行之后为1,执行之前v7为就为0,所以A=52,查找ascall码表结果为4
分析到这,你就认为完了?然后并没有结束、、、、
case 5: {
if (v4 != v5.length) {
v1 = v0.length - 3;
} else {
v4 = 0;
}
break;
}
上面的分析的是v4 != v5.length结果,如果v4 = v5.length呢,继续往下分析吧,也就是输入的字符串为7位
now array:1
now array:1
now array:1
now array:1
now array:1
now array:0
now array:0
now array:1
now array:1
now array:1
now array:5
now array:2
now array:0
now array:0
now array:3
v5 52------A 52 v7 0
now array:2
now array:3
now array:2
now array:3
now array:2
now array:0
now array:0
now array:3
now array:2
now array:3
now array:2
now array:3
now array:2
now array:3
now array:4
v7 1------v4 7
控制v7的的case只有一个
case 3: {
if (v5 == A) {
System.out.println("v5 " + v5 + "------A " + A + " v7 " + v7);
++v7;
} else {
}
break;
}
根据上面的代码循环,可以慢慢倒推出来结果为4ndr01d,真是一波三折呀。。。
放下分析的代码
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class ctf1 {
private static byte[] A;
private static byte[] B;
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
a("4ndr01d");
check();
}
public static void a(String arg5) {
int v1 = 30;
A = new byte;
B = arg5.getBytes();
int v0;
for (v0 = 0; v0 < arg5.length(); ++v0) {
A = B;
}
B = new byte;
}
public static int b(int arg4) {
int v0 = 181 & arg4;
return (v0 & 1) + ((v0 & 4) >> 2) + ((v0 & 16) >> 4) + ((v0 & 32) >> 5) + ((v0 & 128) >> 7);
}
public static boolean check() {
boolean v9 = false;
int[] v0 = new int[] { 40, 42, 65, 67, 68, 2, 64, 70, 96, 98, 181, 7, 10, 64, 23, 17, 37, 20, 45, 91, 74, 72,
135, 33, 57, 43, 87, 99, 147, 53 };
byte[] v5 = new byte[] { 52, 111, 102, 113, 52, 52, 98 };
int v2 = 0;
int v4 = 0;
int v7 = 0;
int v1;
for (v1 = 0; v1 < v0.length; ++v1) {
int v8 = b(v0);
new String();
System.out.println("now array:" + String.valueOf(v8));
switch (v8) {
case 0: {
A = ((byte) (A ^ v7));
break;
}
case 1: {
if (A != 0) {
++v4;
} else {
}
break;
}
case 2: {
v5 = ((byte) (v5 ^ v4));
++v4;
break;
}
case 3: {
if (v5 == A) {
System.out.println("v5 " + v5 + "------A " + A + " v7 " + v7);
++v7;
} else {
}
break;
}
case 4: {
if (v7 == v4) {
v9 = true;
}
System.out.println("v7 " + v7 + "------v4 " + v4);
return v9;
}
case 5: {
if (v4 != v5.length) {
v1 = v0.length - 3;
} else {
v4 = 0;
}
break;
}
default: {
++v2;
break;
}
}
}
return v9;
}
}
三、Alictf
好啦,终于可以上主菜了~~~~~~
先用jeb反编译
package com.ali.mobisecenhance;
import android.app.Application;
import android.content.Context;
public class StubApplication extends Application {
static {
try {
Class v2 = Class.forName("android.os.SystemProperties");
Object v1 = v2.getDeclaredMethod("get", String.class).invoke(v2, "ro.product.cpu.abi");
}
catch(Exception v3) {
v3.printStackTrace();
}
if(((String)v1).equalsIgnoreCase("x86")) {
System.loadLibrary("mobisecx");
}
else {
System.loadLibrary("mobisec");
}
}
public StubApplication() {
super();
}
protected native void attachBaseContext(Context arg1) {
}
public native void onCreate() {
}
}
加了壳子~~~~关键代码看不到,那就先用ida脱壳吧
先打开DDMS
获取入口点
一键挂起
用ida附加
在ibdvm的dvmDefineClass下了个断点,通过DvmDex的指针,把odex dump下来,下好断点,点jdb启动
查看r0地址
跟随上面的地址
用脚步dump下来
static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen("C:\\dump.dex", "wb");
end_addr = 0x75512000 + 0x00010000;
for ( dex_addr = 0x75512000; dex_addr < end_addr; dex_addr ++ )
fputc(Byte(dex_addr), fp);
}
然后修复,最后用版主的Apktool工具,反编译dex,再回编译,就可以用jeb打开了
下面就是分析java代码了
主要是根据这个表对我们输入的字符串,进行置换
static {
e.a = new HashMap();
e.a("a", ". _");
e.a("b", "_ . . .");
e.a("c", "_ . _ .");
e.a("d", "_ . .");
e.a("e", ".");
e.a("f", ". . _ .");
e.a("g", "_ _ .");
e.a("h", ". . . .");
e.a("i", ". .");
e.a("j", ". _ _ _");
e.a("k", "_ . _");
e.a("l", ". _ . .");
e.a("m", "_ _");
e.a("n", "_ .");
e.a("o", "_ _ _");
e.a("p", ". _ _ .");
e.a("q", "_ _ . _");
e.a("r", ". _ .");
e.a("s", ". . .");
e.a("t", "_");
e.a("u", ". . _");
e.a("v", ". . . _");
e.a("w", ". _ _");
e.a("x", "_ . . _");
e.a("y", "_ . _ _");
e.a("z", "_ _ . .");
e.a("2", ". _ _ _ _");
e.a("1", ". . _ _ _");
e.a("3", ". . . _ _");
e.a("4", ". . . . _");
e.a("0", ". . . . .");
e.a("6", "_ . . . .");
e.a("9", "_ _ . . .");
e.a("8", "_ _ _ . .");
e.a("7", "_ _ _ _ .");
e.a("5", "_ _ _ _ _");
}
public e() {
super();
}
public String a(String arg8) {
String v0;
dn.b(dn.a());
if(arg8.equals("...___...")) {
v0 = "sos";
}
else {
StringBuilder v2 = new StringBuilder();
String[] v3 = arg8.split("\\s+");//根据空格进行分割
int v4 = v3.length;
int v1 = 0;
while(true) {
if(v1 < v4) {
Object v0_1 = e.a.get(v3); //替换
if(v0_1 != null) {
v2.append(((String)v0_1));//连接
++v1;
continue;
}
else {
break;
}
}
else {
goto label_26;
}
return v0;
}
throw new IllegalArgumentException();
label_26:
v0 = v2.toString();
}
return v0;
}
下面这部分代码,根据压缩包里的strings.xml和public.xml可以分析run()返回值为0时,提示成功的字符
public void handleMessage(Message arg5) {
dn.b(dn.a());
int v2 = -65536;
this.a.b.setEnabled(true);
switch(arg5.what) {
case 0: {
goto label_10;
}
case 1: {
goto label_27;
}
case 2: {
goto label_51;
}
case 3: {
goto label_66;
}
}
return;
label_66:
this.a.a.setTextColor(v2);
TextView v1 = this.a.a;
int v0 = Main.a(this.a).nextBoolean() ? 2130968582 : 2130968581;
v1.setText(v0);
return;
try {
label_51:
this.a.a.setTextColor(-65536);
}
catch(Exception v0_1) {
this.a.a.setTextColor(-7829368);
}
this.a.a.setText(2130968580);
return;
label_10:
this.a.a.setTextColor(-16776961);
try {
this.a.a.setText(103 / arg5.what);
}
catch(Exception v0_1) {
this.a.a.setText(2130968586);
}
return;
label_27:
this.a.a.setTextColor(v2);
switch(Main.a(this.a).nextInt(3)) {
case 0: {
goto label_36;
}
case 1: {
goto label_41;
}
case 2: {
goto label_46;
}
}
return;
label_36:
this.a.a.setText(2130968583);
return;
label_41:
this.a.a.setText(2130968585);
return;
label_46:
this.a.a.setText(2130968584);
}
下面的run()函数为关键点,只要返回0即可,分析逻辑和上面那个CTF很类似
b(a arg1, Handler arg2, String arg3) {
this.d = arg1;
this.b = arg2;
this.input = arg3;
super();
}
public void run() {
int v0_6;
String v0_5;
String v1_5;
byte[] v2_3;
Cipher v1_1;
MessageDigest v0_3;
String str;
dn.b(dn.a());
MessageDigest v1 = null;
if(Build$VERSION.SDK_INT >= 10 && (Debug.isDebuggerConnected())) {
this.b.sendEmptyMessage(1);
return;
}
try {
str = new e().a(this.input);//a函数的主要目的,在莫尔斯码表中置换
}
catch(Exception v0) {
this.b.sendEmptyMessage(3);
return;
}
try {
if(str.equals("sos")) {
this.b.sendEmptyMessage(2);
return;
}
CRC32 v0_1 = new CRC32();//crc加密
v0_1.update(str.getBytes());
v0_1.getValue();
str.hashCode();
try {
v0_3 = MessageDigest.getInstance("sha1"); //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_26;
}
int v2_1 = 2;
try {
v1_1.init(v2_1, new SecretKeySpec(Base64.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes(), 0), "AES")); //AES设置key
goto label_69;
}
catch(Exception v0) {
}
catch(InvalidKeyException v2_2) {
label_69:
try {
new byte;
try {
v2_3 = v1_1.doFinal(Base64.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8"), 0));//AES解密,值固定
goto label_78;
}
catch(UnsupportedEncodingException v1_2) {
try {
v1_2.printStackTrace();
label_78:
String v6 = new String(v2_3); //上面AES解密的
v0_3.update(new byte[]{127});
v0_3.update(str.getBytes());
v0_3.update(new byte[]{1});
}
catch(Exception v0) {
goto label_26;
}
}
catch(BadPaddingException v1_3) {
goto label_78;
}
catch(IllegalBlockSizeException v1_4) {
goto label_78;
}
}
catch(Exception v0) {
goto label_26;
}
try {
v1_5 = new String(Base64.encode(v0_3.digest(), 0));//
goto label_99;
}
catch(Exception v0) {
try {
v0.printStackTrace();
return;
label_99:
if(!str.equals(v6)) {
goto label_190;
}
else if(Arrays.equals(v1_5.getBytes(), "2398lj2n".getBytes())) {
this.b.sendEmptyMessage(0);
return;
}
else {
v0_5 = "234";
}
goto label_117;
}
catch(Exception v0) {
goto label_26;
}
}
label_190:
v0_5 = v1_5;
try {
label_117:
if(v0_5.equals("lsdf==")) {
this.b.sendEmptyMessage(0);
return;
}
char[] v1_6 = str.toCharArray();
v0_6 = str.substring(0, 2).hashCode();
if(v0_6 > 3904) {
this.b.sendEmptyMessage(4);
return;
}
if(v0_6 == 3618) {
if(v1_6 + v1_6 != 168) {
goto label_178;
}
do {
label_144:
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_188;
}
else if(v1_6 != v5_1) {
v0_6 = 0;
}
else {
++v0_6;
continue;
}
goto label_170;
}
}
goto label_177;
}
while(true);
}
goto label_178;
}
catch(Exception v0) {
goto label_26;
}
label_188:
v0_6 = 1;
try {
label_170:
if(v0_6 != 0) {
this.b.sendEmptyMessage(0);
return;
}
label_177:
if(v2_3 == null) {
goto label_144;
}
label_178:
this.b.sendEmptyMessage(1);
}
catch(Exception v0) {
label_26:
this.b.sendEmptyMessage(1);
}
}
}
只有为这个的时候this.b.sendEmptyMessage(0);才会提示成功, label_188: ---label_117:---label_190:,跳转这三个点才能成功,
label_190:
v0_5 = v1_5;
try {
label_117:
if(v0_5.equals("lsdf==")) {
this.b.sendEmptyMessage(0);
return;
}
v0_3.update(new byte[]{127});v0_3.update(str.getBytes());
v0_3.update(new byte[]{1});
v1_5 = new String(Base64.encode(v0_3.digest(), 0));
v0_5 = v1_5;
由于前面对v0_3的处理,导致不能相等,所以只剩下label_170:这个独苗,然后看可以谁跳转到label_170:
char[] v1_6 = str.toCharArray();
v0_6 = str.substring(0, 2).hashCode();
if(v0_6 > 3904) {
this.b.sendEmptyMessage(4);
return;
}
if(v0_6 == 3618) {
if(v1_6 + v1_6 != 168) {
goto label_178;
}
do {
label_144:
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_188;
}
else if(v1_6 != v5_1) {
v0_6 = 0;
}
else {
++v0_6;
continue;
}
goto label_170;
}
}
goto label_177;
}
while(true);
}
goto label_178;
}
分析可知,v0_6 == 3618 和 v1_6 + v1_6 = 168必须同时成立,v0_6 = str.substring(0, 2).hashCode();,由于前面置换表只有小写字母和数字,所以可以直接遍历
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arry[] = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 };
int n = sizeof(arry) / sizeof(arry);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if ((arry * 31 + arry) == 3618)
{
printf("%d----%d\n", arry,arry);
}
}
}
system("pause");
return 0;
}
c语言很垃圾,只能写这个了.........结果有两个113----115和115----53,只有后面的符合要求,转换成acsll为s5,接着往下看
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_188;
}
else if(v1_6 != v5_1) {
v0_6 = 0;
}
else {
++v0_6;
continue;
}
goto label_170;
}
}
下面就可以看出来是比较了,要长度相等,字符相等,所以后面的为7e1p,加上前面的s5为s57e1p,然后再置换一下就行了,结果为... _____ ____. . ..___ .__.
贴下java的分析代码,很垃圾····························
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class aestest {
public static void main(String[] args) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException{
byte[] v2_3;
Cipher v1_1;
String v5 = "s57e1p";
String v1_5;
int v0_6;
MessageDigest v0_3;
char[] v1_6 = v5.toCharArray();
v0_3 = MessageDigest.getInstance("sha1");
v1_1 = Cipher.getInstance("AES");
Decoder decoder = Base64.getDecoder();
Encoder encode = Base64.getEncoder();
v1_1.init(2, new SecretKeySpec(decoder.decode("GXiQHT1CZ2elMzwpvvAoPA==".getBytes()), "AES"));
v2_3 = v1_1.doFinal(decoder.decode("hjdsUjIT5je69WXIZP7Kzw==".getBytes("UTf-8")));
System.out.println(v2_3);
v0_3.update(new byte[]{127});
v0_3.update(v5.getBytes());
v0_3.update(new byte[]{1});
String v6 = new String(v2_3);
v1_5 = new String(encode.encode(v0_3.digest()));
System.out.println(v1_5);
v0_6 = v5.substring(0, 2).hashCode();
System.out.println(v0_6);
}
} 钉钉CTF这一道,还有一种情况没有分析,就是当case5时候v4 = v5.length,会是一个另一个截然不同的分析方向,大致就是v4先加累加到7,然后case5后v7再累加到7,这就需要对输入的字符串有要求了,会进行些亦或比较操作,这个flag答案为4ndr01d。 C_Ryan 发表于 2018-10-4 03:07
钉钉CTF这一道,还有一种情况没有分析,就是当case5时候v4 = v5.length,会是一个另一个截然不同的分析方向 ...
666,这么多答案吗....当时分析出一个,就没再仔细看了:$qqq 又一个看不懂系列 看不懂系列,哈哈哈 看不懂系列, 不知所云
看不懂系列, 不知所云 向大佬致敬
感谢楼主分享,支持一下!{:1_893:} 请问下LZ dump后的odex怎么修复,是直接用baksmali -x 转dex 还是需要手动修复什么? 你即便把它全部逆向了又能怎么样呢? 能把这几个学会也挺厉害的