探索黑客技术攻防,实战研究与安全创新

导航菜单

解密百度阅读器远程执行

将APK拖到第一个编辑框中,点击反编译APK,稍等片刻解压完成后会生成与文件名同名的文件夹,进入里面可以发现多出了一个smali文件夹,这个文件夹里面存放的就是我们需要分析的 Smali文件。我们双击打开,可以发现里面竟然有几个文件夹及几千个smali文件!!我首次打开时也吃了一惊,如此多的文件让人怎么分析啊(大家可以发怵一下).

而且每个文件的文件名也很诡异,全是一些a-z的字母组合,打开任意一个smali文件看看,发现里面的方法名也是如此。分析过C#程序的人可能会觉得这里似乎很类似,是的,这些代码如同 C#程序里面的混淆器混淆过一样,在 Android程序开发的 SDK中,提供了一套名叫 Proguard的代码保护机制,它的作用就是通过随机化改变类中的方法及变量名,去除无用的虚方法、注释等手段让代码变得难以阅读,以此来达到保护程序的目的,在这里可能很多人再次发怵,本来就没弄过Android程序,现在一上来就是个混淆过的款,叫人怎么活啊!不要急,其实,被Proguard混淆过的代码还是有办法弄的,如有主要的几个类,如Service、Activity等类名是不能被混淆的,不过这不是我们今天要讲的重点。

我们接着分析,回想一下,我们在安装运行程序时,程序弹出了一个Message,想想我们在OD中破解Windows程序时用到的字符串参考,在这里是否可用呢?回答是:可以!我们在编译Android程序时,定义的字符串都保存在一个名收Strings.xml的文件中,编译生成APK后它也被加密打包到程序中了,而程序通过为每个字符串分配一个ID来进行调用,这些 ID被保存在 public.xml文件中。我们在刚才反编译的 XML文件中找找就可以发现在res/values下有这两个文件,我们打开strings.xml并查找之,很快可以发现如下两行:string name=expired_title产品过期/stringstring name=expired_message这是一个有使用期限的版本,从2012年1月1日开始此版本已过期,请确保网络通畅,并立即检查升级。

/string这两行字符串正是前面运行程序时弹出的 MessageBox的标题与文字。我们在这里记下它们的名字为expired_title与expired_message。打开 strings.xml文件查找expired_title与expired_message则可以找到下面两行:public type=string name=expired_title id=0x7f060166 /public type=string name=expired_message id=0x7f060167 /找到了这两个ID就好办了,我们下面来看看Android程序中是如何构建一个字符串的,修改刚才的HelloAndroid代码如下:


public class HelloAndroidActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ShowString();
}
public void  ShowString(){
String str = getString(R.string.hello);
Toast.makeText(getApplicationContext(), str,Toast.LENGTH_SHORT).show();
}
}


重新生成APK后并编译代码如下:


# virtual methods
.method public ShowString()V
.locals 3
.prologue
.line 16
const/high16 v1, 0x7f04
invoke-virtual
{p0,
v1},
Lcn/feicong/HelloAndroid/HelloAndroidActivity;-getString(I)Ljava/lang/String;
move-result-object v0
.line 17
.local v0, str:Ljava/lang/String;
invoke-virtual
{p0},
Lcn/feicong/HelloAndroid/HelloAndroidActivity;-getApplicationContext()Landroid
/content/Context;
move-result-object v1
const/4 v2, 0x0
invoke-static
{v1,
v0,
v2},
Landroid/widget/Toast;-makeText(Landroid/content/Context;Ljava/lang/CharSequen
ce;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;-show()V
.line 18
return-void
.end method
.method public onCreate(Landroid/os/Bundle;)V
.locals 1
.parameter savedInstanceState
.prologue
.line 11
invoke-super
{p0,
p1},
Landroid/app/Activity;-onCreate(Landroid/os/Bundle;)V
.line 12
const/high16 v0, 0x7f03
invoke-virtual
{p0,
v0},
Lcn/feicong/HelloAndroid/HelloAndroidActivity;-setContentView(I)V
.line 13
invoke-virtual
{p0},
Lcn/feicong/HelloAndroid/HelloAndroidActivity;-ShowString()V
.line 14
return-void
.end method


可以看到 const/high16 v1, 0x7f04将 V1寄存器的高 16位设置为0x7f04,低位未设置为0,然后调用getString()将结果保存到v0并设置到str中,在这里注意一下invoke-virtual之类调用的格式,在OpCode后面的括号中是参数列表,用逗号分隔,后面'L'打头的是相应参数的数据类型,用分号隔开,大写字符'I’表示为int类型。我们在反编译的文件夹中搜索调用了 ID为0x7f060167文件。发现R$string.smali与DocumentManager.smali两个文件中有调用,第1个为代码编写时自动生成的,可以无视,第2个就是重点了,我们打开DocumentManager.smali文件查找调用处,代码为:


methodstaticsynthetice(Lcn/wps/moffice/documentmanager/DocumentManager;)V.locals 3iget-objectv0,p0,
Lcn/wps/moffice/documentmanager/DocumentManager;-cPj:Lcn/wps/moffice/writer/view/beans/g;
if-eqz v0, :cond_0  //如果 g对象获取失败就跳到过期提示,这里是爆破点(g就是我们见到的过期提示框)
iget-objectv0,p0,
Lcn/wps/moffice/documentmanager/DocumentManager;-cPj:Lcn/wps/moffice/writer/view/beans/g;
invoke-virtual {v0},  Lcn/wps/moffice/writer/view/beans/g;-isShowing()Z//g对象是否已经显示
move-result v0if-nez v0, :cond_1  //如果g显示了就直接返回就直接返回:cond_0
new-instance v0, Lcn/wps/moffice/writer/view/beans/g;
//创建一个g的实例
invoke-direct
{v0,p0},
Lcn/wps/moffice/writer/view/beans/g;-init(Landroid/content/Context;)V  //


构造函数


const v1, 0x7f060166  //字符串常量产品过期
invoke-virtual{v0,v1},
Lcn/wps/moffice/writer/view/beans/g;-eb(I)Lcn/wps/moffice/writer/view/beans/g;
move-result-object v0   //调用g的eb()方法,应该是设置弹出框的标题
const v1, 0x7f060167  //字符串常量这是一个有使用期限的版本,从2012年1月1日开始此版本已过期,请确保网络通畅,并立即检查升级。
invoke-virtual {p0, v1},
Lcn/wps/moffice/documentmanager/DocumentManager;-getString(I)Ljava/lang/String;
move-result-object v1   //取得字符串
invoke-virtual{v0,v1},
Lcn/wps/moffice/writer/view/beans/g;-be(Ljava/lang/String;)Lcn/wps/moffice/writer/view/beans/g;
move-result-object v0//调用g的be()方法,应该是设置弹出框的内容const v1, 0x7f060112new-instance v2, Lcn/wps/moffice/documentmanager/DocumentManager$4;invoke-direct{v2,p0},
Lcn/wps/moffice/documentmanager/DocumentManager$4;-init(Lcn/wps/moffice/documentmanager/DocumentManager;)Vinvoke-virtual
//构造DocumentManager{v0,v1,v2},
Lcn/wps/moffice/writer/view/beans/g;-a(ILandroid/content/DialogInterface$OnClickListener;)Lcn/wps/moffice/writer/view/beans/g;move-result-object v0//设置按钮监听器
const v1, 0x7f060165new-instance v2, Lcn/wps/moffice/documentmanager/DocumentManager$7;
invoke-direct{v2,p0},Lcn/wps/moffice/documentmanager/DocumentManager$7;-init(Lcn/wps/moffice/documentmanager/DocumentManager;)V  //DocumentManager对象invoke-virtual{v0,v1,v2},
Lcn/wps/moffice/writer/view/beans/g;-b(ILandroid/content/DialogInterface$OnClickListener;)Lcn/wps/moffice/writer/view/beans/g;move-result-object v0//设置按钮监听器
iput-objectv0,p0,Lcn/wps/moffice/documentmanager/DocumentManager;-cPj:Lcn/wps/moffice/writer/view/beans/g;iget-objectv0,p0,Lcn/wps/moffice/documentmanager/DocumentManager;-cPj:Lcn/wps/moffice/writer/view/beans/g;const/4 v1, 0x0invoke-virtual{v0,v1},p0,Lcn/wps/moffice/writer/view/beans/g;-setCancelable(Z)V //设置是否可取消
iget-objectv0,Lcn/wps/moffice/documentmanager/DocumentManager;-cPj:Lcn/wps/moffice/writer/view/beans/g;invoke-virtual {v0}, Lcn/wps/moffice/writer/view/beans/g;-show()V   //显示g,也就是过期对话框
:cond_1return-void.end method


这个e方法看得出来是重要的地方,我做了详细的注释,功能就是首先g是否已经显示,如果没显示就初始化并显示,我们爆破就简单了,直接if-eqzv0,:cond_0改成if-eqzv0, :cond_1。保存文件并退出,拖动不整个文件夹到ApkTool_Gui第二个编辑框重建APK。

完成后拿到Android设备上测试。会发现程序弹出个Toast提示软件过期,然后就退出了,看来还有地方需要处理。按照上面的方法如法炮制搜索 0x7f060166(软件过期字符串的ID),发现有两处调用,而且两处调用都是类似的代码:


.methodpublicfinal
a(Lcn/wps/moffice/documentmanager/DocumentManager$a;Ljava/lang/String;)V.locals 4invoke-static{},
Lcn/wps/moffice/OfficeApp;-zc()Lcn/wps/moffice/OfficeApp;
move-result-object v0invoke-virtual {v0}, Lcn/wps/moffice/OfficeApp;-zd()Z  ///对程序进行验证
move-result v0if-eqz v0, :cond_1 //验证为0就跑去运行程序
const v0, 0x7f060166invoke-virtual//软件过期字符串ID
{p0,v0},
Lcn/wps/moffice/documentmanager/DocumentManager;-getString(I)Ljava/lang/String;
move-result-object v0const/4 v1, 0x0invoke-static{p0,v0,v1},
Landroid/widget/Toast;-makeText(Landroid/content/Context;Ljava/lang/CharSequen
ce;I)Landroid/widget/Toast;move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;-show()V//弹出了Toast
iget-objectv0,p0,Lcn/wps/moffice/documentmanager/DocumentManager;-DY:Lcn/wps/moffice/documentmanager/history/HistoryFiles;iget-objectv1,p0,Lcn/wps/moffice/documentmanager/DocumentManager;-cPk:Ljava/lang/Runnable;const-wide/16 v2, 0x7d0invoke-virtual{v0,v1,v2,v3},
Lcn/wps/moffice/documentmanager/history/HistoryFiles;-postDelayed(Ljava/lang/Runnable;J)Z  //这里就OVER了
:cond_0:goto_0return-void:cond_1
invoke-static{},Lcn/wps/moffice/OfficeApp;-zc()Lcn/wps/moffice/OfficeApp;
move-result-object v0invoke-virtual{p1},
Lcn/wps/moffice/documentmanager/DocumentManager$a;-toString()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Lcn/wps/moffice/OfficeApp;-bO(Ljava/lang/String;)Zmove-result v0


同样,两处的修改很简单,只需要将 if-eqz v0, :cond_1改成 if-nez v0, :cond_1就可以了。保存文件后重新回编,安装测试就会发现程序被破解掉了。

到这里,本文就结束了,总结一下,内容不多,主要介绍了Android程序分析需要用到的工具及Android程序的一般分析方法,另外,在实战中也看到了,混淆并不可怕,可怕的是没有耐心坚持下去。关于Android程序的破解分析我会继续下去,目前已知有另一种更高级的方法,不过我目前测试还没成功,等小有成果就拿来与大家分享。

百度阅读器是一款向用户提供文字阅读功能的工具性软件,它支持主流文档格式,如txt、pdf、doc、ppt等,还支持在线阅读书籍,可以借助网络阅读更多的文档。该软件的使用界面也十分清爽,如图1所示。


图片1.png

在使用百度阅读器阅读文档的时候,该软件模拟真实书籍阅读方式,将文档展现的用户面前,从这一方面来讲,确实显得非常引人喜欢,如图2所示。

图片2.png

在将百度阅读器安装进系统的时候,该软件自带了一个ActiveX控件,文件名为“BRIEPlugin1.1.0.274.dll”。使用ComRaider查看该控件提供的所有外部接口,如图3所示。

图片3.png

从图3中看到,“BRIEPlugin1.1.0.274.dll”提供的外部接口非常丰富,在我们的测试中发现,出现问题的外部接口较多,而这里最引起我们关注的是一个名为“OpenPage”的外部接口。该外部接口的函数原型为:Sub OpenPage (ByVal strPageURL As String)

从函数原型我们发现,“OpenPage”的参数只有一个,其属于字符串型。面对这种参数,一般情况下,我们都会考虑对其进行缓冲区溢出测试,但是,这里我们却不这样考虑。借助OllyDbg我们对“BRIEPlugin1.1.0.274.dll”文件进行了反汇编分析,发现其中调用了一个不安全函数“ShellExecuteW”,如图4所示。

图片4.png

“ShellExecuteW”是一个可以运行任何文件的系统函数,我们怀疑该函数在某种情况下,通过调用“BRIEPlugin1.1.0.274.dll”文件的外部接口可以触发该函数。很幸运,“OpenPage”这个外部接口就是“ShellExecuteW”函数的调用者。我们可以写一个简单的测试网页代码来证明这个分析,网页代码如下所示。


objectclassid=clsid:4EA36CDF-1236-45E8-B7E8-B6140C617A21
name=evil /object
script
document.write(evil.OpenPage(cmd.exe));
/script


保存上面的代码为index.htm,上传该网页文件到服务器上,用IE访问该网页,效果如图5所示.

图片5.png

图5的画面已经足以说明一切,“BRIEPlugin1.1.0.274.dll”文件的“OpenPage”确实调用了“ShellExecuteW”函数,而“strPageURL”这个参数就代表了被运行文件的名称。现在,我们可以发挥自己的想象力来构造出任意exploit网页,例如使用死循环调用程序造成用户系统死机、采用欺骗页面诱使用户运行远程木马等等。百度阅读器出现的这个安全漏洞,从实质上说是一个执行任意文件的安全漏洞,危害较大,尤其是漏洞发生在ActiveX控件上,这就使得该漏洞可以实现远程攻击的效果。编程人员在设计该ActiveX控件的时候,可能没有仔细考虑到安全方面的要求,疏忽了对系统函数的调用进行安全封装。“ShellExecuteW”函数的作用不仅仅限于运行可执行文件,凡是系统注册、有相关联类型的文件都可以被成功运行,还有协议类,如 mailto。面对这样的函数,编程人员应该对用户提交的参数进行严格审核,微软在2010年就对“ShellExecuteW“函数出现的安全漏洞进行了修补,其性质类似于此。

本文为网络安全技术研究记录,文中技术研究环境为本地搭建或经过目标主体授权测试研究,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均在文章发布时已修复,在挖掘、提交相关漏洞的过程中,应严格遵守相关法律法规。