记一次Android逆向算法分析《阿里聚安全攻防挑战赛预赛第一题》
本帖最后由 skywilling 于 2017-5-12 19:01 编辑0x00前言
之前因为学习过Android编程,所以对Android逆向一直抱着强烈的兴趣,这就像网络中的攻与防,两者相互促进、相互发展,所以说搞开发的或者搞安全的都很有必要学习研究一下当前研究领域的对立领域,这或许会在将来发挥出意想不到的作用。前面说的有点正式了{:1_918:},其实吧,搞逆向,说白了就是破解{:1_907:},好了,废话就不再多说了,下面开始正题。
0x01效果图
下面是运行的结果图,很简单的界面只有两个控件,一个输入框,一个按钮。
0x02Java代码
Java代码很简单,可以看出来,我们要找的就是native层的verify()方法
public class MainActivity
extends Activity
{
public static String TAG = "hellojni";
public static native boolean verify(String paramString);
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
requestWindowFeature(1);
setContentView(2130903065);
Button localButton = (Button)findViewById(2131427338);
localButton.setBackgroundResource(2130837579);
localButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramAnonymousView)
{
System.loadLibrary("crackme");
Object localObject = ((EditText)MainActivity.this.findViewById(2131427333)).getText().toString();
String str = "warnning! it is an incorrect key!";
int i = Color.rgb(255, 0, 0);
if (MainActivity.verify((String)localObject))
{
str = "congratulation! You found the key!";
i = Color.rgb(0, 255, 0);
}
localObject = (TextView)MainActivity.this.findViewById(2131427336);
((TextView)localObject).setTextColor(i);
((TextView)localObject).setText(str);
}
});
}
0x03分析so文件
分析so文件我们一般都用动态调试的方法,静态调试对这个问题来说不太适合。
这里我就不再说明动态调试的方法,默认读者都会。
这里我主要想说的是该算法的加密过程(一般只要写出了加密的代码,写出解密的代码就很轻松了,所以在这里,只说一下加密的过程)。
下面是加密的大概流程:
1.异或0x7E
2.异或0x21
3.异或0x40
4.异或0x23
5.加密1
6.异或0x24
7.加密2
8.异或0x25
流程1,2,3,4,6,8的异或方式是input=input^(index+0x00).
要重点说明一下的是,在流程4和5之间有一个判断字符串长度是否大于0x10(在我首次分析的时候忽略了这个判断条件,以至于用重新分析了一遍{:1_908:},真是蛋疼),在流程4结束后,比较字符串长度,如果小于0x10,以(0x10-len)扩充字符串到0x10,如果大于0x10,以(0x20-len)扩充到0x20。这里我想说的是,这道题目的key的长度是大于0x10,所以我们在调试时,就要输入一个长度大于0x10的数据作为测试数据。
0x04加密1
下面的话,我会完全结合我写的加密代码来进行,所以也请读者结合文章结尾附件中的加密代码来看。
在这里我将加密1的过程用分为了三个步骤:
步骤一:
func1(in,table0);
func1(in,table0);
func6(out,in,table1,table0);
func7(in,table0);
func1(in+0x10,table0);
func1(in+0x10,in);
func6(out,in+0x10,table1,table0);
func7(in+0x10,table0);
方法的名字有些难以理解,就将就看吧,其实整个过程下来,也就这几个方法。
简单说一下这几个方法的作用,
func1(), 简单来说就是和table0(一个置换表,学过密码学的都应该了解)进行异或操作,in=in^table0;
func6(),这是一个比较复杂的加密,
void func6(unsigned char*out,unsigned char* in,unsigned char*table,unsigned char*table1){ int i=1;
for(i=1;i<0xA;i++){
strcopy(out,in,0x10);
func2(in,out,table);
func4(in);
func5(in,table1,i);
}
strcopy(out,in,0x10);
func2(in,out,table);
}
可以看出func6实际上就是需要func2,func4,func5依次加密9次,再进行一次func2加密的结果,可以算作一个整体。
func2,也是一种置换,代码如下
void func2(unsigned char*out,unsigned char*in,unsigned char*table){
unsigned char n=in;
out=func3(n,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
n=in&0xFF;
out=func3(n,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
n=in&0xFF;
out=func3(n,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
n=in&0xFF;
out=func3(n,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
out=func3(in&0xFF,table);
}
unsigned char func3(unsigned char*table,int i){
return table;
}
func4,具体我也不清楚这个加密的意图,我是按照汇编语言翻译过来的,
void func4(unsigned char*in){
unsigned char r0,r1,r2,r3,r4,r5,r6,r7,r9;
unsigned char r8=0;
int i=0;
for(i=0;i<0x10;i+=4){
r0=in;
r1=in;
r2=in;
r3=in;
r4=r1^r0;
r5=r4&0xFF;
r6=r4^r2;
r7=r8-(r5>>7);
r7=r7&0x1B;
r5=r7^(r5<<1);
r7=r6^r3;
r9=r7^r0;
r5=r9^r5;
in=r5;
r5=r7^r1;
r7=r2^r1;
r7=r7&0xFF;
r1=r8-(r7>>7);
r1=r1&0x1B;
r7=r1^(r7<<1);
r5=r5^r7;
r7=r3^r2;
r7=r7&0xFF;
in=r5;
r5=r3^r4;
r2=r8-(r7>>7);
r2=r2&0x1B;
r7=r2^(r7<<1);
r5=r5^r7;
in=r5;
r5=r3^r0;
r5=r5&0xFF;
r7=r8-(r5>>7);
r7=r7&0x1B;
r5=r7^(r5<<1);
r5=r5^r6;
in=r5;
}
func5,这个加密还是有一定的规律的,
void func5(unsigned char*in,unsigned char*table, unsigned char n){ unsigned char r7,r1,r0;
int i=0;
r7=n;
r1=in;
r0=table;
r0=r1^r0;
in=r0;
for(i=1;i<0x10;i++){
r0=i;
r0=r0|(r7<<4);
r1=in;
r0=table;
r0=r1^r0;
in=r0;
}
}
func7,这个加密和func1差不多,只是开始的位置不同,in=in^table
以上就是加密1的步骤一。
步骤二:
//1
func1(next,table2);
func6(out,next0,table1,table2);
func7(next0,table2);
//2
func1(next+0x10,next0);
strcopy(next0+0x10,next+0x10,0x10);
func1(next0+0x10,table2);
func6(out,next0+0x10,table1,table2);
func7(next0+0x10,table2);
//3
memset(next+0x20,0x10,0x10);
func1(next+0x20,next0+0x10);
strcopy(next0+0x20,next+0x20,0x10);
func1(next0+0x20,table2);
func6(out,next0+0x20,table1,table2);
func7(next0+0x20,table2);
步骤三:
func7(key1,table2);
func11(out,key1,table3,table2);
func1(key1,table2);
func1(key1,table2);
//2
func7(key1+0x10,table2);
func11(out,key1+0x10,table3,table2);
func1(key1+0x10,table2);
func1(key1+0x10,next0);
//3
func7(key1+0x20,table2);
func11(out,key1+0x20,table3,table2);
func1(key1+0x20,table2);
func1(key1+0x20,next0+0x10);
这里唯一不一样的就是,func6变成了func11,说一下,func6与func11的关系,
=======
func6 table1 table0
func11table3table0
=======
func6table1table2
func11table3table2
=======
func6table1table4
func11 table3table4
=======
两两互为逆过程,也就是加解密过程,所以这里方便了加解密代码的编写。
0x05加密2
加密又可分为两个步骤:
步骤一:
//1
func1(key3,table4);
strcopy(key2,key3,0x10);
func1(key3,table4);
func6(out,key3,table1,table4);
func7(key3,table4);
//2
func1(key2+0x10,key3);
strcopy(key3+0x10,key2+0x10,0x10);
func1(key3+0x10,table4);
func6(out,key3+0x10,table1,table4);
func7(key3+0x10,table4);
//3
func1(key2+0x20,key3+0x10);
strcopy(key3+0x20,key2+0x20,0x10);
func1(key3+0x20,table4);
func6(out,key3+0x20,table1,table4);
func7(key3+0x20,table4);
步骤二:
//1
func7(key4,table4);
func11(out,key4,table3,table4);
func1(key4,table4);
func1(key4,table4);
//2
strcopy(key4+0x10,key3+0x10,0x10);
func7(key4+0x10,table4);
func11(out,key4+0x10,table3,table4);
func1(key4+0x10,table4);
func1(key4+0x10,key3);
//3
strcopy(key4+0x20,key3+0x20,0x10);
func7(key4+0x20,table4);
func11(out,key4+0x20,table3,table4);
func1(key4+0x20,table4);
func1(key4+0x20,key3+0x10);
0x06结语
第一次发帖有点小紧张,凡事都有第一次,谨以此文纪念第一次在吾爱发帖。当然,更多的是,在n年以后,想起我曾在某某知名论坛发过贴,想想也有一种欣慰。最后,希望本文可以对新手们有所启发。
附件:链接:http://pan.baidu.com/s/1cEOxFC 密码:mwxa
版权声明:允许转载,但是一定要注明出处。 @ydydq 有很多的反编译apk的工具,但是他们大部分都是基于apktool和dex2jar制作的。个人而言,Android killer(因为很长时间不更新了,所以反编译一些新平台的apk就会出现失败或卡死情况)要比改之理好用,这两个工具爱盘里面都有。 watchdoge 发表于 2017-5-16 22:41
楼主,运行你的c文件提示segmentation fault了。。。
是有这种问题,编写的时候我用的codeblock,他好像没有检测数组越界的功能,最近换了vs2017之后才发现这个问题,解决方法是将定义的next0数组的大小改成0x30,原来是0x20 first blood 看不懂,但是支持一下~~ 有点意思了 每天总能学习新的东西 thx f or share. 学习了,谢谢楼主分享 支持一下~~ 哈哈,居然给加了精华{:301_975:} 谢谢楼主的分享,这些能有很大的帮助