与其说是逆向题,不如说是一道简单的算法题。
目录
分析
对文件进行解压、反汇编后查看文件,定位到MainActivity.java
。
MainActivity
//
// Decompiled by Procyon v0.5.30
//
package com.iscc.crackme;
import android.content.Context;
import android.widget.Toast;
import android.view.View;
import android.view.View$OnClickListener;
import android.widget.EditText;
import android.widget.Button;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity
{
static {
System.loadLibrary("native-lib");
}
private boolean checkFirst(final String s) {
if (s.length() != 16) {
return false;
}
for (int i = 0; i < s.length(); ++i) {
if (s.charAt(i) > '8') {
return false;
}
if (s.charAt(i) < '1') {
return false;
}
}
return true;
}
public native boolean checkSecond(final String p0);
@Override
protected void onCreate(final Bundle bundle) {
super.onCreate(bundle);
this.setContentView(2131296284);
this.findViewById(2131165218).setOnClickListener((View$OnClickListener)new View$OnClickListener() {
final /* synthetic */ EditText val$editText = this.findViewById(2131165240);
public void onClick(final View view) {
final String trim = this.val$editText.getText().toString().trim();
if (MainActivity.this.checkFirst(trim) && MainActivity.this.checkSecond(trim)) {
// '注册成功!'
Toast.makeText((Context)MainActivity.this, (CharSequence)"\u6ce8\u518c\u6210\u529f!", 0).show();
return;
}
// '注册失败!'
Toast.makeText((Context)MainActivity.this, (CharSequence)"\u6ce8\u518c\u5931\u8d25!", 0).show();
}
});
}
}
checkFirst
先进行初步检查,要求字符长度为16,而且字符只能是数字1-8的ascii码
。
private boolean checkFirst(final String s) {
if (s.length() != 16) {
return false;
}
for (int i = 0; i < s.length(); ++i) {
if (s.charAt(i) > '8') {
return false;
}
if (s.charAt(i) < '1') {
return false;
}
}
return true;
}
而checkSecond
应该在libnative-lib.so
里面,用IDA进行查看。
Java_com_iscc_crackme_MainActivity_checkSecond
char __fastcall Java_com_iscc_crackme_MainActivity_checkSecond(__int64 a1, __int64 a2, __int64 a3)
{
char result; // al
char v4; // [rsp+6h] [rbp-8Ah]
char v5; // [rsp+13h] [rbp-7Dh]
char v6; // [rsp+40h] [rbp-50h]
char v7; // [rsp+58h] [rbp-38h]
char v8; // [rsp+70h] [rbp-20h]
unsigned __int64 v9; // [rsp+88h] [rbp-8h]
v9 = __readfsqword(0x28u);
jstring2str((__int64)&v8, a1, a3);
v5 = 0;
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v7, &v8);
v4 = 0;
if ( checkfirst((__int64)&v7) & 1 )
{
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v6, &v8);
v5 = 1;
v4 = checkAgain((__int64)&v6);
}
if ( v5 & 1 )
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v6);
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v7);
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v8);
result = v4 & 1;
if ( __readfsqword(0x28u) == v9 )
result = v4 & 1;
return result;
}
可以看出这里面又有两个检查。
checkfirst
int __fastcall checkfirst(char *str)
{
char *v2; // [rsp+0h] [rbp-118h]
char *v3; // [rsp+18h] [rbp-100h]
signed int i; // [rsp+30h] [rbp-E8h]
char v5; // [rsp+37h] [rbp-E1h]
for ( i = 1; i < 8; ++i )
{
if ( *str & 1 )
v3 = (char *)*((_QWORD *)str + 2);
else
v3 = str + 1;
if ( *str & 1 )
v2 = (char *)*((_QWORD *)str + 2);
else
v2 = str + 1;
// 后面的字符的码值必须要比前面的大
if ( v3[i] <= v2[i - 1] )
{
v5 = 0;
return v5 & 1;
}
}
v5 = 1;
return v5 & 1;
}
这里检查前面的八个字符,规则就是后面的字符的码值必须要比前面的大。那么很明显就是前面八个字符就是12345678
了。
这里提一点:
if ( *str & 1 )
v3 = (char *)*((_QWORD *)str + 2);
else
v3 = str + 1;
对于上面的判断我们不用太在意,应该是JAVA的一种规定,字符串的第一个值是用来存放标志位的。总之我们可以直接看成是v3 = str + 1;
语句就对了。
checkAgain
int __fastcall checkAgain(char *str)
{
int result; // eax
char *v2; // [rsp+10h] [rbp-170h]
char *v3; // [rsp+20h] [rbp-160h]
int l; // [rsp+3Ch] [rbp-144h]
int k; // [rsp+40h] [rbp-140h]
int j; // [rsp+44h] [rbp-13Ch]
signed int i; // [rsp+48h] [rbp-138h]
char key; // [rsp+4Fh] [rbp-131h]
int v9[8]; // [rsp+130h] [rbp-50h]
int v10[10]; // [rsp+150h] [rbp-30h]
unsigned __int64 v11; // [rsp+178h] [rbp-8h]
v11 = __readfsqword(0x28u);
for ( i = 0; i < 8; ++i )
{
if ( *str & 1 )
v3 = (char *)*((_QWORD *)str + 2);
else
v3 = str + 1;
v10[i] = v3[i] - '1';
}
for ( j = 0; j < 8; ++j )
{
if ( *str & 1 )
v2 = (char *)*((_QWORD *)str + 2);
else
v2 = str + 1;
v9[j] = v2[j + 8] - '1';
}
result = v9[7] + v9[0];
if ( v9[7] + v9[0] == 5 )
{
result = v9[6] + v9[1];
if ( v9[6] + v9[1] == 12 )
{
result = v9[0];
if ( v9[0] < v9[7] )
{
for ( k = 1; k < 8; ++k )
{
for ( l = 0; l < k; ++l )
{
result = k; // 不能相等
if ( v10[l] == v10[k] )
{
key = 0;
goto LABEL_34;
}
result = k;
if ( v9[l] == v9[k] )
{
key = 0;
goto LABEL_34;
}
result = l; // 后面的减去差不能相等
if ( v10[k] - v10[l] == v9[k] - v9[l] )
{
key = 0;
goto LABEL_34;
}
result = k; // 这里把上面的判断反过来了,也就是对负数进行了判断
if ( v10[k] - v10[l] == v9[l] - v9[k] )
{
key = 0;
goto LABEL_34;
}
}
result = k + 1;
}
key = 1;
}
else
{
key = 0;
}
}
else
{
key = 0;
}
}
else
{
key = 0;
}
LABEL_34:
LOBYTE(result) = key;
if ( __readfsqword(0x28u) == v11 )
result = key & 1;
return result;
}
这里就考验我们的基础算法了,只要写出对应的算法进行解方程就行。
算法脚本
#!/usr/bin/python
# -*- coding:utf-8 -*-
import itertools
array = [0, 1, 2, 3, 4, 5, 6, 7]
list_all = list(itertools.permutations(array, 8))
for v in list_all:
if( v[0] + v[7] == 5 and
v[1] + v[6] == 12 and
v[0] < v[7]):
end = False
for i in range(1, 8):
if(end):
break
j = 0
while(j < i):
if( i - j == v[j] - v[i] or
i - j == v[i] - v[j]):
end = True
break
j += 1
if(end == False):
print(v)
output = ''
for value in v:
output += str(value + 1)
print(output)
# exit(0)
运行实例
ex@Ex:~/test$ python main.py
(2, 5, 1, 6, 4, 0, 7, 3)
36275184
所以说后面的八个字符就是36275184
,结合前面的字符,那么flag就是1234567836275184
了。
总结
算法是非常重要的,以后一定要好好学习算法。