前言:
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1582287-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 静态分析
1.将程序下载下来,发现是个apk文件,那就用jadx工具打开看看,先看看主函数,看oncreate函数,一般这就是启动函数了,可以看到程序逻辑就是调用m1a方法,根据返回值决定成功还是失败。
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { // from class: com.a.easyjni.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
if (MainActivity.this.m1a(((EditText) ((MainActivity) this).findViewById(R.id.edit)).getText().toString())) {
Toast.makeText(this, "You are right!", 1).show();
} else {
Toast.makeText(this, "You are wrong! Bye~", 1).show();
}
}
});
}
2.再来看m1a方法,发现是new了一个C0678类对象,然后调用m0a方法得到一个返回值,然后通过ncheck去验证。
public boolean m1a(String str) {
try {
return ncheck(new C0678a().m0a(str.getBytes()));
} catch (Exception e) {
return false;
}
}
3. 再去看看C0678a这个类,创建类对象时会初始化一个静态字符串f2481a,再看m0a方法,接收一个前面我们在文本框输入的字符串,然后很像base64编码的过程,左右移位,f2481a就是码表,这里仔细看可以看到for循环对我们的字符串进行移位,每三位一组处理成4个字符,最后不足的末尾补=号,转换后的字符串存在sb中,然后返回这个字符串。
public class C0678a {
/* renamed from: a */
private static final char[] f2481a = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
/* renamed from: a */
public String m0a(byte[] bArr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= bArr.length - 1; i += 3) {
byte[] bArr2 = new byte[4];
byte b = 0;
for (int i2 = 0; i2 <= 2; i2++) {
if (i + i2 <= bArr.length - 1) {
bArr2[i2] = (byte) (b | ((bArr[i + i2] & 255) >>> ((i2 * 2) + 2)));
b = (byte) ((((bArr[i + i2] & 255) << (((2 - i2) * 2) + 2)) & 255) >>> 2);
} else {
bArr2[i2] = b;
b = 64;
}
}
bArr2[3] = b;
for (int i3 = 0; i3 <= 3; i3++) {
if (bArr2[i3] <= 63) {
sb.append(f2481a[bArr2[i3]]);
} else {
sb.append('=');
}
}
}
return sb.toString();
}
}
4.返回值看明白了,现在去看ncheck方法是怎么验证的,跟过去之后发现是一个native方法,那就只能换IDA继续跟了,找到so文件用IDA打开,搜索java,找到了ncheck函数,有些看不懂这些赋值操作,不要晕,先看返回值,result如果是0那肯定就是失败了,所以result必须是1,然后往回看到memcmp比较函数,也就是说v12必须等于"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"。
v5 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
if ( strlen(v5) == 32 )
{
for ( i = 0; i != 16; ++i )
{
v7 = &v12[i];
v12[i] = v5[i + 16];
v8 = v5[i];
v7[16] = v8;
}
(*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
v9 = 0;
do
{
v10 = v9 < 30;
v13 = v12[v9];
v12[v9] = v12[v9 + 1];
v12[v9 + 1] = v13;
v9 += 2;
}
while ( v10 );
result = memcmp(v12, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
}
else
{
(*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
result = 0;
}
return result;
}
5.现在看看v12,32位的字符串,现在假设v5就是我们输入的字符串编码后的形式,这段代码就好理解了,分为两个循环,第一个for循环就是将v5的前16位与后16位对调赋值给v12。
6.再看第二个do while循环v9每次自增2,把中间三行挑出来单独看,这不就是典型的交换两个变量的值嘛,那这里就是每次循环取v12中的两个值,然后交换,最终和"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"比较,相等,就说明验证通过了。
v13 = v12[v9];
v12[v9] = v12[v9 + 1];
v12[v9 + 1] = v13;
0x2 动态分析
1.运行一下程序,用JEB动态调试看看,发现报错了,原来是上次把root权限给关了造成的,重新打开即可。
2.CTRL+B在jeb中打上断点,将apk拖进jeb,然后在输入123456点check按钮,断了下来,直接运行到函数末尾,看右侧变量,可以知道我输入的123456,经过base64编码得到:"f4G91LqC"。
3.在打开IDA调试so文件,回忆一下步骤adb shell,mount -o rw,remount /,重新挂载一下文件系统,adb push android_x86_server /sbin ,chmod +x /sbin/android_x86_server,给它可执行权限,然后输入android_x86_server,运行起来监听23946端口,adb forward tcp:23946 tcp:23946命令,将手机上的23946窗口,转发到我们电脑本地的23946端口。
4.然后一顿操作下来,懵了,不知道咋调试,本来还想验证下v5是不是我想的那样,这只能算了。
0x3 计算flag
1.把"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"作为结果反推。
2.还原so第二个循环do while,每两个变量前后位置交换
3.还原so第一个循环for,交换前16位和后16位
4.编写base64解码程序,直接把之前自己实现的c++版搬运过来即可:https://www.52pojie.cn/thread-1212431-1-1.html。
5.得到最终的解密代码:
package ctf;
import java.util.Base64;
public class test01 {
public static void main(String[] args) {
char[] mb = { 'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y',
'4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x',
'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J',
'R', 'Z', 'N' };
String output = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
StringBuffer flag = new StringBuffer("");
StringBuffer f_flag = new StringBuffer("");
int i = 0;
// 每两个变量交换前后的值
do {
flag.append(output.charAt(i + 1));
flag.append(output.charAt(i));
i += 2;
} while (i <= 30);
System.out.println("flag1:" + flag);
// 交换前16位和后16位
for (i = 0; i != 16; ++i) {
char temp = flag.charAt(i);
flag.setCharAt(i, flag.charAt(i + 16));
flag.setCharAt(i + 16, temp);
}
System.out.println("flag2:" + flag);
// base64解码得到flag
i = 0;// 密文下标
byte[] b_flag = flag.toString().getBytes();
while (i < b_flag.length)// 判断是否解密完毕
{
int index = 0;// 码表下标
int temp1 = -1;// 保存第一个密文下标
int temp2 = -1;// 保存第二个密文下标
int temp3 = -1;// 保存第三个密文下标
int temp4 = -1;// 保存第四个密文下标
// 通过码表反查密文对应的下标,然后分别获得十进制数字
for (; index < 64; index++) {
if (b_flag[i] == mb[index]) {
temp1 = index;
}
if (b_flag[i + 1] == mb[index]) {
temp2 = index;
}
if (b_flag[i + 2] == mb[index]) {
temp3 = index;
}
if (b_flag[i + 3] == mb[index])// 一轮密文4个字节已取出,退出循环
{
temp4 = index;
}
// 如果已经查到四个下标就退出循环!
if (temp1 != -1 && temp2 != -1 && temp3 != -1 && temp4 != -1)
break;
}
if (temp3 != -1 && temp4 != -1)// 完整的读取到了4个字节,直接解密
{
f_flag.append((char) ((temp1 << 2) | (temp2 >> 4)));// 取第一个密文6个字符再加上第二个密文前2个字符
f_flag.append((char) (((temp2 & 15) << 4) | (temp3 >> 2))); // 取第二个密文后4个字符再加上第三个密文前4个字符
f_flag.append((char) (((temp3 & 3) << 6) | temp4)); // 取第三个密文后2个字符再加上第四个密文6个字符
} else if (temp3 == -1)// 只取到了2个字节
{
f_flag.append((char) ((temp1 << 2) | (temp2 >> 4))); // 取第一个密文6个字符再加上第二个密文前2个字符
} else if (temp4 == -1)// 只取到了3个字节
{
f_flag.append((char) ((temp1 << 2) | (temp2 >> 4)));// 取第一个密文6个字符再加上第二个密文前2个字符
f_flag.append((char) ((temp2 & 15) << 4 | (temp3 >> 2)));// 取第二个密文后4个字符第三个密文前4个字符
} else {
System.out.println("下标取值不对,程序逻辑错误!!!");
}
i += 4;// 密文每次循环向后移动4位
}
System.out.println("flag3:" + f_flag);
}
}
6.运行,得到flag:“flag{justANot#er@p3}”。
flag1:bM3TQsXg30i9g3==QAoOQMPFks1BsB7c
flag2:QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
flag3:flag{just_ANot#er_@p3}