Android与windows软件安全基础

Android与windows软件安全基础
青少年总是充满好奇心,你我大家都一样,从最开始的windows软件逆向分析破解到android软件的逆向我的兴趣有增无减,正所谓站在巨人的肩膀上我们可以看得更远,下面我就以一些我看过的书和例子(《android软件安全与逆向分析》,《黑客破解精通》《0day软件安全》《黑客防线2009缓冲区溢出攻击与防范专辑》)简单的分析下windows平台和android下软件安全的一些东西包括破解,溢出攻击,反破解的基础知识。 为了照顾初学者我就用简单的例子吧,windows是大家最先接触的系统,所以就从win下开始分析一个简单的小程序我给大家粘贴点源码看看,在vc6下编译可以通过,注意build要用debug版本,而不是release

1
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    authenticated=strcmp(password,PASSWORD);
    return authenticated;
}
main()
{
    int valid_flag=0;
    char password[1024];
    while(1)
    {
        printf("please input password:       ");
        scanf("%s",password);
        valid_flag = verify_password(password);
        if(valid_flag)
        {
            printf("incorrect password!\n\n");
        }
        else
        {
            printf("Congratulation! You have passed the verification!\n");
            break;
        }
    }
}

相信大家都看得懂吧,简略说一下这是一个密码验证程序,你输入的密码是1234567的话就会出现Congratulation! You have passed the verification!,程序中有一个if判断,就是有两个跳转一个正确一个错误,为了让大家看的清晰我把编译好的程序附加到ida中给大家看看
image

我补充一下学习软件安全是要有一定的汇编语言基础的,win下的话有好多书籍资料,android下的话要学习一下dalvik指令和ARM汇编语言,顺带介绍一下win的反汇编工具olldbg和IDA,android程序的反汇编工具apktool很流行,但IDA更清晰好用,win和android程序都可以用IDA来反汇编,好了再把它附加到od中分析,通过字符串搜索直接来到关键位置我把汇编语言代码粘贴出来

90 nop
1
00401040  /$  81EC 00040000 sub esp,0x400
00401046  |>  68 88804000   /push crack_me.00408088                  ;  please input password:
0040104B  |.  E8 57000000   |call crack_me.004010A7
00401050  |.  8D4424 04     |lea eax,dword ptr ss:[esp+0x4]
00401054  |.  50            |push eax                                ;  kernel32.BaseThreadInitThunk
00401055  |.  68 84804000   |push crack_me.00408084                  ;  %s
0040105A  |.  E8 31000000   |call crack_me.00401090
0040105F  |.  8D4C24 0C     |lea ecx,dword ptr ss:[esp+0xC]
00401063  |.  51            |push ecx
00401064  |.  E8 97FFFFFF   |call crack_me.00401000
00401069  |.  83C4 10       |add esp,0x10
0040106C  |.  85C0          |test eax,eax                            ;  kernel32.BaseThreadInitThunk
0040106E  |.  74 0F         |je short crack_me.0040107F
00401070  |.  68 6C804000   |push crack_me.0040806C                  ;  incorrect password!\n\n
00401075  |.  E8 2D000000   |call crack_me.004010A7
0040107A  |.  83C4 04       |add esp,0x4
0040107D  |.^ EB C7         \jmp short crack_me.00401046
0040107F  |>  68 38804000   push crack_me.00408038                   ;  Congratulation! You have passed the verification!\n
00401084  |.  E8 1E000000   call crack_me.004010A7
00401089  |.  81C4 04040000 add esp,0x404
0040108F  \.  C3            retn

我大体说明下汇编指令的意义,call就是调用函数,jmp就是无条件跳转,add esp,xx是用来恢复堆栈的,我在说下函数调用时的堆栈变化
1父函数将函数的实参从右往左压入堆栈
2cpu将父函数中函数调用指令Call xxxx的下一条指令地址eip压入堆栈
3父函数通过Push Ebp指令将基址指针EBP值压入堆栈,并通过Mov Ebp,Esp
指令将当前堆栈指针Esp值传递给Ebp
4通过Sub Esp,m为存放在函数中的局部变量开辟内存,函数执行中可以通过EBP来指引完成
上面汇编代码给的很清晰,有跳向正确和错误的跳转分别是
0040107D |.^ EB C7 \jmp short crack_me.00401046

0040106E |. 74 0F |je short crack_me.0040107F
我们用只用把跳向错误的那条指令改成不跳向就可以随便输入都显示正确了,je的意思就是等于则跳我们把它改成相反的不等于跳jne,
0040106E |. 74 0F |jne short crack_me.0040107F
保存一下程序就破解了
这段开始分析溢出部分,刚才那个程序还有其他破解方法,比如输入有的8个字符的字符串,字符串中隐藏的第9个阶段符NULL就能将authenticated低字节中的1覆盖成0,这样就绕过了验证程序达到同样效果比如输入rrrrrrrr,2345678等等,下面在原来代码基础下变更一下,这些代码都是我从随书光盘上找的我声明下哦《0day安全软件漏洞分析技术》,别说我侵权呵呵,

1
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[44];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);//over flowed here! 
    return authenticated;
}
main()
{
    int valid_flag=0;
    char password[1024];
    FILE * fp;
    LoadLibrary("user32.dll");//prepare for messagebox
    if(!(fp=fopen("password.txt","rw+")))
    {
        exit(0);
    }
    fscanf(fp,"%s",password);
    valid_flag = verify_password(password);
    if(valid_flag)
    {
        printf("incorrect password!\n");
    }
    else
    {
        printf("Congratulation! You have passed the verification!\n");
    }
    fclose(fp);
}

没有增加多少,就是多了个打开记事本因为有些字符输入不了,还多了个LoadLibrary为了装载user32.dll来调用MessageBox演示溢出,还是先说下简单栈溢出的原理吧,因为c语言不进行边界检查,我们程序中也没有对输入的安全判断,一些危险的函数如strcpy拷贝字符串的过程中会发生溢出,如例子中的strcpy向buffer中拷贝字符串,一旦超过buffer所能容纳的数量就会发生溢出,如果拷贝的数据是乱的可能系统会报错,因为那些数据淹没了返回地址,如果你精巧的构造一下呵呵,就可以为所欲为了,那段代码就叫做shellcode,网络上流传的利用原理分三步
1定位返回点,我记得有个报错对话框精确定位返回点的方法
2shellcode编写,网上现成的好多
3跳到shellcode,把返回点覆盖成jmp esp的地址
我们这段代码比较简单就不用那么复杂了直接来,按照书上的方法在password.txt中写入11组“4321”共44个字符,然后od中看看,拉进去下断点运行如图
image
现在思路清晰了,我的实际地址和作者的不同可能是系统版本之类的原因吧呵呵,现在简单了我们找到了返回地址在buff后8个字符位置,也就是图中的0018FAEC,数组的起始位置也在图中0018FAB8,现在只需要准备拿起准备好的shellcode以16进制的形式填充到password.txt中,要注意在53~56个字节出粘贴数组的起始位置0018FAB8,也就是更改掉函数的返回地址执行我们的shellcode如作者书中的对话框程序已近成功弹出,好了就这么简单,更加深入的大家可以自由发挥,比如activx,内核级别,还有好多软件。
有了一些win下的逆向经验,相信在android的apk上好多思路也是同样适用的,它们还有好多相同点和不同点,在开始之前我们先来补充一点基础知识,android软件的逆向分析要理解Dalvik opcodes,它就像在分析win软件是的汇编语言一样,然后还需要学习ARM汇编语言,当然这是深入型的了,我留个地址给大家看看,讲的是apk反汇编之smali语法http://bbs.pediy.com/showthread.php?p=1117963
因为我们分析apk主要是分析用apktool逆向出的smali文件,修改好之后重新编译好然后签名就可以运行apk了,smali语法主要是有一些数据类型称之为基类,还有一些方法的语法形式,例如Lpackage/name/ObjectName;->MethodName(III)Z
在这个例子中,你应该识别出“ Lpackage/name/ObjectName; 是一个类, MethodName明显是一个方法名,(III)Z是方法的签名,‘III’在这个例子中是三个整形参数,Z是表示返回一个布尔类型的返回值。
还有几个寄存器,比较简单,寄存器的命名方式:V命名
P命名第一个寄存器就是方法中的第一个参数寄存器
比较:使用P命名是为了防止以后如果在方法中增加寄存器,需要对参数寄存器重新进行编号的缺点
特别说明一下:Long和Double类型是64位的,需要2个寄存器
例如:对于非静态方法
LMyObject——>myMethod(IJZ)V;
有4个参数:LMyObject,int,long,bool; 需要5个寄存器来存储参数;
P0 this
P1 I (int)
P2,P3 J (long)
P4 Z(bool)
好了我从电脑中找一个实例源码来分析吧把它activity层代码粘贴出来

com.droider.crackme0201;
1
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.support.v4.app.NavUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MainActivity extends Activity {
    private EditText edit_userName;
    private EditText edit_sn;
    private Button btn_register; 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle(R.string.unregister);  //模拟程序未注册
        edit_userName = (EditText) findViewById(R.id.edit_username);
        edit_sn = (EditText) findViewById(R.id.edit_sn);
        btn_register = (Button) findViewById(R.id.button_register);
        btn_register.setOnClickListener(new OnClickListener() {            
            public void onClick(View v) {
                if (!checkSN(edit_userName.getText().toString().trim(), 
                        edit_sn.getText().toString().trim())) {
                    Toast.makeText(MainActivity.this,       //弹出无效用户名或注册码提示
                            R.string.unsuccessed, Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this,       //弹出注册成功提示
                            R.string.successed, Toast.LENGTH_SHORT).show();
                    btn_register.setEnabled(false);
                    setTitle(R.string.registered);  //模拟程序已注册
                }                
            }
        });    
    }

1
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    private boolean checkSN(String userName, String sn) {
        try {
            if ((userName == null) || (userName.length() == 0))
                return false;
            if ((sn == null) || (sn.length() != 16))
                return false;
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.reset();
            digest.update(userName.getBytes());
            byte[] bytes = digest.digest();     //采用MD5对用户名进行Hash
            String hexstr = toHexString(bytes, ""); //将计算结果转化成字符串
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hexstr.length(); i += 2) {
                sb.append(hexstr.charAt(i));
            }
            String userSN = sb.toString(); //计算出的SN        
            //Log.d("crackme", hexstr);
            //Log.d("crackme", userSN);
            if (!userSN.equalsIgnoreCase(sn))   //比较注册码是否正确
                return false;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return false;
        }        
        return true;
    }
    
    private static String toHexString(byte[] bytes, String separator) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if(hex.length() == 1){
                hexString.append('0');
            }
            hexString.append(hex).append(separator);
        }
        return hexString.toString();
    }
    
}

Layout层比较简单(两个edittext两个textview一个按钮)就不粘贴了,源码我会在后面留下,代码比较简单大家都能看懂吧开始是一些初始化和一个按钮点击事件,破解这个程序的关键主要是checkSN方法,两个edittext一个用户名输入框一个注册码输入框,注释很清晰了采用MD5对用户名进行Hash,将计算结果转化成字符串,计算出的SN,比较注册码是否正确,正确就会有正确的toast提示反之是错误提示,我们把编译好的apk用apktool打开,它就自动生成了好多smali文件,我们找到R$string.smali这个,因为这个文件定义了字符串常量,包括我们注册成功与失败的字符串常量,找到这条表示成功字符串的东西.field public static final successed:I = 0x7f05000c
然后在右侧搜索框输入0x7f05000c搜索全部,搜索结果有三条,我们要的是这条
MainActivity$1.smali因为里面包含了验证方法我继续粘贴出来
line 32

#calls: Lcom/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z
invoke-static {v0, v1, v2}, Lcom/droider/crackme0201/MainActivity;->access$2(Lcom/droider/crackme0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

move-result v0

.line 33
if-nez v0, :cond_0 如果不为0则跳到cond_0处

.line 34
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

.line 35
const v1, 0x7f05000b  这个位置是不成功的字符串

.line 34
invoke-static {v0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

.line 35
invoke-virtual {v0}, Landroid/widget/Toast;->show()V

.line 42
:goto_0
return-void

.line 37
:cond_0
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

.line 38
const v1, 0x7f05000c 成功的字符串

.line 37
invoke-static {v0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast; toast很明显了嘛

move-result-object v0

.line 38
invoke-virtual {v0}, Landroid/widget/Toast;->show()V

.line 39
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

#getter for: Lcom/droider/crackme0201/MainActivity;->btn_register:Landroid/widget/Button;   设置按钮属性
invoke-static {v0}, Lcom/droider/crackme0201/MainActivity;->access$3(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/Button;

move-result-object v0
上面有一个关键 if-nez v0, :cond_0,我们乱输入用户名和sn的话这个跳转就会实现,不等于0则跳转,我们让他相反吧,改成if-eqz v0, :cond_0,这个跳转指令和nez相反的,类似的跳转指令还有很多,好了我们保存编译,然后签名,随便输入都显示注册成功了,我在网上看了一下现在游戏android软件还有人这样破解,就是反编译后把付费后的函数内容复制到付费前的那个函数里面,达到了破解付费软件的效果,这只是一个简单的例子,现在android确实有了好多代码混淆工具还有壳子,壳比较少,当然这只能组织一些入门级的小白,呵呵,更深入的学习还多呢
关于android溢出,由于java语言的特性比较少,但原理和windows软件相似,前段时间有个Android系统的WebView控件任意命令执行漏洞,android的sdk中封装了webView控件。这个控件主要用开控制的网页浏览。其中webView下有一个非常特殊的接口函数addJavascriptInterface,能实现本地java和js的交互。利用addJavascriptInterface这个接口函数可实现穿透webkit控制android 本机。攻击者可以构建恶意WEB页,诱使用户解析,可以应用程序上下文执行任意命令。Android安全方面主要存在于数据信息的加密,还有一些恶意软件的威胁,一般都容易看出,因为在主activity中会有一些敏感的操作和服务,好了由于只是初级探索我们到此结束,感兴趣的可以继续深入下去,我们需要学习的东西还很多呢
demo地址:http://pan.baidu.com/s/1qWPndRA