您的位置:首页 > 业界新闻

QQ聊天记录查看器的编写

2015-11-29 20:34 246 查看
前段时间对360部分功能进行分析后,我一直在思考手机程序的安全性问题,尽管在此之前,在国内安全论坛也一直没有见到有人讨论过Android平台上隐私安全等话题,见到最多的也就是各类短信、电话监听器的编写。我思索再三,觉得这可能由以下两点原因造成的:其一是目前在国内的Android市场上此类信息窥探软件没有兴起,没有对用户利益造成大面积的伤害,因此安全厂商也无法对其做出必要的需求产品。其二是大多数开发人员和手机自身用户对此类攻击手段并不知晓,安全意识也非常的薄弱。但这些原因并不能阻碍流氓、病毒软件的滋生,相反,在国内市场,这些软件正处于潜伏期,一旦时机成熟,将会在移动互联网上掀起一阵血雨腥风。尽早的学习相关安全知识,加强安全意识,对安全厂商、安全爱好者以及用户都是很有必要的。

今天,我们把目标锁定在国内用户群最大的软件公司腾讯出品的Android版手机QQ上。版本选择了最新的2.3版,而测试环境依然是ROOT过的MOTO XT615上,通过对它简单的分析,发现在手机在获得ROOT权限后可能对用户隐私带来的极大的危害。

一般情况下,Android手机用户在拿到新机后,可能会做以下三件事。

一、水货用户立马重新刷机,然后对手机进行ROOT,国行用户思考再三后也ROOT掉手机(Android手机软件运行在普通用户权限,程序对系统本身没有控制权。而所谓“ROOT掉手机”就是通过一些手段让Android手机可拥有ROOT权限,之后软件通过“ROOT权限”可以随意修改、访问手机中所有资源)

二、将手机ROM中自带的广告软件、无用软件精减掉来对手机进行优化

三、马上装上QQ(这点可能是必须的!原因的话,你懂的)

今天的测试在手机必须拥有ROOT权限下才能完成,这在现实大多数环境下是成立的。

程序分析

手机QQ界面做的很漂亮,UI的布局也很人性化,但使用时发现了点小问题,如果手机用2G网络或者无信号时,QQ登陆会耗费很长时候,期间界面会是黑屏状态,有时候程序会卡死。另外,有时候手机无网络时打开群聊天窗口时,程序也会卡死,估计程序是在消息同步时被阻塞了。

今天分析之前,先说说Dalvik反汇编代码的阅读。有朋友对我直接读Small文件的反编译代码这种方式有些不赞同,认为用JD-GUI工具来阅读DEX转的JAR包更方便,我个人认为,Dalvik 反汇编代码的阅读是必须要掌握的,因为DEX2JAR不是万能的,很多时候DEX2JAR就会失败无法生成JAR文件,使用JD-GUI读JAVA代码固然是好,但从基础上掌握反汇编的阅读也是很重要的。

拿出APK反编译工具将QQ2.3安装包进行反汇编解包。按照国际惯例,先打开“AndroidMenifest.xml”文件,寻找程序启动部分,首先找到“.activity.SplashActivity”文件,从名字上看为启动时的闪屏,找到Activity文件(位于“\smali\com\tencent\mobileqq\activity”文件夹下)并打开,直接看OnCreate()方法,代码如下:

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4
    .parameter
    .prologue
    .line 41
    invoke-super {p0, p1}, Lcom/tencent/mobileqq/app/BaseActivity;->onCreate(Landroid/os/Bundle;)V
    #这里的父类也是TX自己编写的
    .line 42
    sput-object p0, Lcom/tencent/mobileqq/activity/SplashActivity;->instance:Lcom/tencent/mobileqq/activity/SplashActivity;
    .line 43
    invoke-virtual {p0}, Lcom/tencent/mobileqq/activity/SplashActivity;->getIntent()Landroid/content/Intent;
    .line 44
    const/4 v0, 0x1
    invoke-virtual {p0, v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->requestWindowFeature(I)Z
    .line 45
    invoke-virtual {p0}, Lcom/tencent/mobileqq/activity/SplashActivity;->getWindow()Landroid/view/Window;
    move-result-object v0
    const/16 v1, 0x400
    invoke-virtual {v0, v1}, Landroid/view/Window;->addFlags(I)V
    .line 46
    const v0, 0x7f03007f
    invoke-virtual {p0, v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->setContentView(I)V
    #设置要显示View
    .line 47
    new-instance v0, Landroid/os/Handler;  #☻new一个Handler☻
    invoke-direct {v0}, Landroid/os/Handler;-><init>()V
    iput-object v0, p0, Lcom/tencent/mobileqq/activity/SplashActivity;->a:Landroid/os/Handler;  #保存Handler
    .line 52
    iget-object v0, p0, Lcom/tencent/mobileqq/activity/SplashActivity;->a:Landroid/os/Handler;
    iget-object v1, p0, Lcom/tencent/mobileqq/activity/SplashActivity;->a:Ljava/lang/Runnable;
    const-wide/16 v2, 0x7d0 #延迟的时间间隔
    invoke-virtual {v0, v1, v2, v3}, Landroid/os/Handler;->postDelayed(Ljava/lang/Runnable;J)Z
    #☻延迟启动一个线程进行登陆操作☻
    .line 54
    invoke-virtual {p0}, Lcom/tencent/mobileqq/activity/SplashActivity;->getApplication()Landroid/app/Application; #获取Application
    move-result-object p0
    check-cast p0, Lcom/tencent/mobileqq/app/QQApplication;
    .line 55
    invoke-static {p0}, Lcom/tencent/mobileqq/log/ExceptionHandler;->register(Lcom/tencent/mobileqq/app/QQApplication;)V
    #注册QQApplication的异常处理器
    .line 56
    invoke-virtual {p0}, Lcom/tencent/mobileqq/app/QQApplication;->a()Lcom/tencent/qphone/base/remote/SimpleAccount;
    move-result-object v0  #调用a()方法并比较结果,用户是否获取成功
    if-eqz v0, :cond_0
    invoke-virtual {p0}, Lcom/tencent/mobileqq/app/QQApplication;->a()Lcom/tencent/qphone/base/remote/SimpleAccount;
    move-result-object v0
    invoke-virtual {v0}, Lcom/tencent/qphone/base/remote/SimpleAccount;->getSid()Ljava/lang/String;
    move-result-object v0 #getSid()获取SID自动登陆字符串
    if-eqz v0, :cond_0
    .line 58
    invoke-virtual {p0}, Lcom/tencent/mobileqq/app/QQApplication;->a()Lcom/tencent/qphone/base/remote/SimpleAccount;
    move-result-object v0
    invoke-virtual {v0}, Lcom/tencent/qphone/base/remote/SimpleAccount;->getUin()Ljava/lang/String;
    move-result-object v0
    invoke-static {v0}, Lcom/tencent/mobileqq/log/ReportLog;->setUserUin(Ljava/lang/String;)V     
    .line 59
    invoke-virtual {p0}, Lcom/tencent/mobileqq/app/QQApplication;->a()Lcom/tencent/qphone/base/remote/SimpleAccount;
    move-result-object v0
    invoke-virtual {v0}, Lcom/tencent/qphone/base/remote/SimpleAccount;->getSid()Ljava/lang/String;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/String;->getBytes()[B
    move-result-object v0
    invoke-static {v0}, Lcom/tencent/mobileqq/log/ReportLog;->setSig([B)V
   #ReportLog记录结果,到这里用户就可以算是自动登陆了
    .line 65
    :cond_0
    invoke-static {}, Lcom/tencent/qphone/base/util/LoginHelper;->getCommonConfig()Ljava/lang/String;
    move-result-object v0 #听名字是获取通用配置,跟踪发现是JNI调用获取信息,这里可以不跟
    .line 66
    const-string v1, "<QQIniUrl>"
    invoke-virtual {v0, v1}, Ljava/lang/String;->indexOf(Ljava/lang/String;)I
    move-result v1
    .line 67
    const-string v2, "</QQIniUrl>"
    invoke-virtual {v0, v2}, Ljava/lang/String;->indexOf(Ljava/lang/String;)I
    move-result v2
    .line 69
    if-ltz v1, :cond_1
    if-le v2, v1, :cond_1
    .line 70
    const-string v3, "<QQIniUrl>"
    invoke-virtual {v3}, Ljava/lang/String;->length()I
    move-result v3
    add-int/2addr v1, v3
    invoke-virtual {v0, v1, v2}, Ljava/lang/String;->substring(II)Ljava/lang/String;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/String;->trim()Ljava/lang/String;
    move-result-object v0
    .line 71
    sput-object v0, Lcom/tencent/mobileqq/log/ReportLog;->URL_LOG_UPLOAD:Ljava/lang/String;
    .line 72
    invoke-virtual {p0}, Lcom/tencent/mobileqq/app/QQApplication;->a()Lcom/tencent/mobileqq/utils/httputils/HttpCommunicator;
    move-result-object v0
    invoke-static {v0, p0}, Lcom/tencent/mobileqq/log/ReportLog;->upload(Lcom/tencent/mobileqq/utils/httputils/HttpCommunicator;Landroid/content/Context;)V
    .line 74
    :cond_1
    const/4 v0, 0x0
    const-string v1, "SplashActivity onCreate()"
    invoke-static {v0, v1}, Lcom/tencent/mobileqq/log/ReportLog;->appendLog(Ljava/lang/String;Ljava/lang/String;)V
    .line 81
    return-void
.end method
在OnCreate()方法中,创建了一个Handler对象,Handler中加入了一个新线程,其实Handler与主线程使用了同一线程,所以在这里主线程就转去执行它的Run()方法了,新线程的实现代码在“ly.smali”文件中,打开这个文件看Run()方法:

.method public final run()V
    .locals 5
    .prologue
    const-wide/16 v3, 0x0
    .line 107
    sget-object v0, Lcom/tencent/mobileqq/activity/NotificationActivity;->instance:Lcom/tencent/mobileqq/activity/NotificationActivity;
    if-nez v0, :cond_2 #判断NotificationActivity实例是否存在,此处就是在判断QQ是否已经运行
    iget-object v0, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity; #获取SplashActivity对象
    invoke-static {v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->access$000(Lcom/tencent/mobileqq/activity/SplashActivity;)Lcom/tencent/mobileqq/app/QQApplication; #调用access$000()方法,此处判断QQApplication的实例app是否存在
    move-result-object v0
    iget-boolean v0, v0, Lcom/tencent/mobileqq/app/QQApplication;->c:Z #app->c
    if-nez v0, :cond_2
    .line 108
    iget-object v0, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    const-string v1, "mobileQQ"
    const/4 v2, 0x0
    invoke-virtual {v0, v1, v2}, Lcom/tencent/mobileqq/activity/SplashActivity;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences; #getSharedPreferences是获取QQ目录SharedPreference配置信息
    move-result-object v0
    .line 110
    const-string v1, "firstTimeRun"
    invoke-interface {v0, v1, v3, v4}, Landroid/content/SharedPreferences;->getLong(Ljava/lang/String;J)J
    move-result-wide v1 #获取首次启动时间
    .line 112
    cmp-long v1, v1, v3 #判断配置文件中的启动时间是否为空
    if-nez v1, :cond_0
    .line 113
    invoke-interface {v0}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor; #准备写入数据
    move-result-object v0
    .line 114
    const-string v1, "firstTimeRun"
    const-wide/16 v2, 0x1
    invoke-interface {v0, v1, v2, v3}, Landroid/content/SharedPreferences$Editor;->putLong(Ljava/lang/String;J)Landroid/content/SharedPreferences$Editor;
    #写入首次启动时间
    .line 115
    invoke-interface {v0}, Landroid/content/SharedPreferences$Editor;->commit()Z #提交修改
    .line 116
    iget-object v0, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    invoke-static {v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->access$100(Lcom/tencent/mobileqq/activity/SplashActivity;)V
    .line 118
    :cond_0
    iget-object v0, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    invoke-virtual {v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->finish()V #结束闪屏
    .line 119
    new-instance v0, Landroid/content/Intent;
    iget-object v1, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    const-class v2, Lcom/tencent/mobileqq/activity/HomeActivity;
    invoke-direct {v0, v1, v2}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
    #构造一个HomeActivity对象
 .line 120
    iget-object v1, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    invoke-virtual {v1}, Lcom/tencent/mobileqq/activity/SplashActivity;->getIntent()Landroid/content/Intent;
    move-result-object v1
    const-string v2, "selfuin"
    invoke-virtual {v1, v2}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
    move-result-object v1
    .line 121
    invoke-static {v1}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z
    move-result v1
    if-nez v1, :cond_1
    .line 123
    iget-object v1, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    invoke-virtual {v1}, Lcom/tencent/mobileqq/activity/SplashActivity;->getIntent()Landroid/content/Intent;
    move-result-object v1
    invoke-virtual {v0, v1}, Landroid/content/Intent;->putExtras(Landroid/content/Intent;)Landroid/content/Intent;
    .line 125
    :cond_1
    iget-object v1, p0, Lly;->a:Lcom/tencent/mobileqq/activity/SplashActivity;
    invoke-virtual {v1, v0}, Lcom/tencent/mobileqq/activity/SplashActivity;->startActivity(Landroid/content/Intent;)V #启动HomeActivity
    .line 128
    :cond_2
    return-void
.end method
在线程中,获取并保存程序首次启动时间,并启动了HomeActivity,我们继续跟进,直接看HomeActivity的OnCreate()方法,不过它实在是太长了,我就不帖了,整个过程就是验证QQ自动登陆的,如果其间发生了错误或自动登陆验证失败会执行下面的代码:

    .line 166
    :cond_a
    new-instance v0, Landroid/content/Intent;
    const-string v1, "com.tencent.mobileqq.action.LOGIN"
    invoke-direct {v0, v1}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V
    const/high16 v1, 0x4
    invoke-virtual {v0, v1}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;
    move-result-object v0
    const/16 v1, 0x3e8
    invoke-virtual {p0, v0, v1}, Lcom/tencent/mobileqq/activity/HomeActivity;->startActivityForResult(Landroid/content/Intent;I)V
    goto :goto_3
在“AndroidMenifest.xml”文件中可以发现 "com.tencent.mobileqq.action.LOGIN"对应的就是LoginActivity,打开“LoginActivity.smali”文件,直接看登陆按钮监听器代码吧,同样,代码老长的,只上关键部分:

.method public onClick(Landroid/content/DialogInterface;I)V
    .locals 5
    .parameter
    .parameter
    .prologue
    const/4 v1, -0x1
    .line 811
    iget v0, p0, Lcom/tencent/mobileqq/activity/LoginActivity;->a:I
    if-eq v0, v1, :cond_3
   
............
    .line 836
    :cond_2
    invoke-virtual {v0}, Lhf;->notifyDataSetChanged()V
    .line 837
    iget-object v0, p0, Lcom/tencent/mobileqq/activity/LoginActivity;->e:Landroid/widget/CheckBox;
    invoke-virtual {v0}, Landroid/widget/CheckBox;->isChecked()Z
    move-result v0
    if-eqz v0, :cond_3
    .line 838
    new-instance v0, Lcom/tencent/mobileqq/data/QQEntityManagerFactory;
    invoke-direct {v0, v2}, Lcom/tencent/mobileqq/data/QQEntityManagerFactory;-><init>(Ljava/lang/String;)V
    .line 839
    invoke-virtual {v0, v2}, Lcom/tencent/mobileqq/persistence/EntityManagerFactory;->build(Ljava/lang/String;)Landroid/database/sqlite/SQLiteOpenHelper;
    move-result-object v1
    invoke-virtual {v1}, Landroid/database/sqlite/SQLiteOpenHelper;->getWritableDatabase()Landroid/database/sqlite/SQLiteDatabase;
    move-result-object v1
    .line 840
    const-string v2, "select name from sqlite_master where type=\"table\" and name like \"mr_%\""
    const/4 v3, 0x0
    invoke-virtual {v1, v2, v3}, Landroid/database/sqlite/SQLiteDatabase;->rawQuery(Ljava/lang/String;[Ljava/lang/String;)Landroid/database/Cursor;
    move-result-object v2
    .line 844
    :goto_0
    invoke-interface {v2}, Landroid/database/Cursor;->moveToNext()Z
    move-result v3
    if-eqz v3, :cond_4
    .line 845
    const/4 v3, 0x0
    invoke-interface {v2, v3}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;
    move-result-object v3
    .line 846
    invoke-static {v3}, Lcom/tencent/mobileqq/persistence/TableBuilder;->dropSQLStatement(Ljava/lang/String;)Ljava/lang/String;
    move-result-object v3
    invoke-virtual {v1, v3}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
    goto :goto_0
    :catch_0
    move-exception v0
    .line 856
    :cond_3
    :goto_1
    return-void
    .line 848
    :cond_4
    new-instance v2, Lcom/tencent/mobileqq/data/RecentUser;
    invoke-direct {v2}, Lcom/tencent/mobileqq/data/RecentUser;-><init>()V
    invoke-virtual {v2}, Lcom/tencent/mobileqq/data/RecentUser;->getTableName()Ljava/lang/String;
    move-result-object v2
    invoke-static {v2}, Lcom/tencent/mobileqq/persistence/TableBuilder;->dropSQLStatement(Ljava/lang/String;)Ljava/lang/String;
    move-result-object v2
    invoke-virtual {v1, v2}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
    .line 850
    invoke-virtual {v0}, Lcom/tencent/mobileqq/persistence/EntityManagerFactory;->close()V
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
    goto :goto_1
.end method
这段代码注释我就不写了,大家自己看看也能明白,它在执行一条“select name from sqlite_master where type=\"table\" and name like \"mr_%\"”SQL语句,作用就是查询表中所有以“mr_”打头的表名,这在Sqlite命令行下输入“.tables mr_%”功能是一样的。看来,TX保存信息是使用的Sqlite.。

分析到这里,有必要对Android的存储机制先有些简单的了解。在Android中,信息的存储有以下几种,第一类存储为普通的文件存储,通过JAVA提供的IO库对文件读取与写入,这与其它的语言或平台是一样的。显然,由于它直接将数据暴露给用户,这种存储机制的安全性是最低的。第二类是Android中提供的一个私有数据存储类“SharedPerferences”,它提供了简单的键值对来对数据进行的存储,上次分析的360手机卫士就是用这种方法保存数据,这个类存储的数据只能被软件自身访问(ROOT过的手机除外),对数据安全性本身进行了加强。第三类是SQLite数据库存储,有过开发经验的朋友可能都认识它,它是一个轻量级的关系型数据库,现在各大平台上应用都很广泛了,在Android系统中,它作为了一个标准数据库的存在。第四类是ContentProvider方式存储,在第二类与第三类数据在存储的数据都是程序私有的,然而新的问题是两个或多个程序有时候可能需要进行数据交换,如同一个公司产品的无缝结合等,解决这个问题就要靠ContentProvider了,一个ContentProvider接口实现了一套标准的方法接口,应用程序可以通过实现ContentProvider接口将自己的数据暴露出去,这样就做到了选择性的数据开放。第五类就是网络存储了,也就是通过网络来实现对数据的存储与获取,此类存储一般作为软件的附加功能的多。

SQLite数据库一般保存在“/data/data/<package_name>/databases/”目录下,<package_name>为软件的包名。通过“adb shell”或DDMS进入手机“/data/data/com.tencent.mobileqq/databases/”目录会发现里面有好几个以“.db”结尾的文件(前提是手机必须ROOT,不然是无法查看的),并且其中一个是以自己QQ号命名的。

接下来试着自己手动查看一下这个db文件的信息,打开了命令行,首先输入“chcp 65001”,如图1所示:

 

图 1

然后,CMD标题栏上右键选择“属性”,设置字体为“Lucida Console”,如图2所示:

 

图 2

做这两步工作是将字符集从“ANSI”变成“UTF-8”,否则,显示中文字符会乱码。下一步是导出以自己名字命名的DB文件(在我的手机上,没有Sqlite3程序),我直接将db文件导出后用sqlite3.exe(这个文件包含在Android SDK中,运行前先配置好环境变量)打开,如图3所示:

 

图 3

列举出数据库中含有的表名后,查看其中Friends表的字段,如图4所示:

 

图 4

通过对字段的猜测执行下面的SQL语句:“select uin, name, groupid from Friends;”,结果如图5所示:

 

图 5

可以神奇的发现,我QQ中的好友都被列举出来了!!,剩下的工作就是分析出每个表的含义,然后构造SQL语句来获取数据,分析出的数据如表1所示:

表名

存储的数据

Card

可能是身份信息

FriendInfo

好友的信息

FriendMore 

更多好友 ,最新的说说与朋友QQ号

Friends 

好友列表

Groups 

好友分组

RecentUser 

最新好友

TroopInfo 

QQ群

TroopMemberInfo 

QQ群成员信息

TroopSelfInfo

QQ群自身信息

VideoAbility 

有摄像头的好友

mr_friend_XXX

好友聊天记录(XXX为好友QQ号)

mr_troop_XXX

群聊天记录(XXX为QQ群号)

表 1

 

代码编写

在数据表名称与含义获取完成后,就是写代码了,先整理一下思路:程序首先运行到QQ相关的目录获取数据库,然后复制到自身目录下,通过Android提供的Sqlite操作类对数据进行解析,构造不同的页面并显示出来:

首先分析下架构,程序需要显示出本地QQ的好友列表、群列表、QQ消息、群消息,可分页显示消息。因此需要以下的几个操作类:

u QQFriendService类负责QQ好友的操作,包括获取好友列表、好友QQ号、好友分组。

u QQTroopService类负责群的操作,包括获取群列表、群号、群的备注信息。

u QQMessageService负责QQ好友与群的消息操作,查询并显示聊天消息。

u PageUtil负责聊天消息的分页查询。

完整的工程项目代码结构如图6所示:

 

图 6

三个Service类都要有查询列表、数目等操作,因此可抽象出一个“IQQService”接口:

public interface IQQService{
public List<Object> queryList(int startIndex, int endIndex);
public long queryCount();
public ArrayList<HashMap<String, String>> setAdapterListData(int startIndex, int endIndex);
}
setAdapterListData()方法是为返回ListView的列表项数据而设计的。

而PageUtil 类调用了“IQQService”的几个操作方法来返回数据:

public class PageUtil {
public static int pagesize = 20;
public int currentpage;
private IQQService service;
private int count;
private int pagecount;

public PageUtil(IQQService service){
super();
this.service = service;
currentpage = 1;
count = new Long(service.queryCount()).intValue();
pagecount = count / pagesize;
if(count % pagesize != 0) pagecount++;
Log.i("QQMsgLook", String.valueOf(currentpage) + ' '
+ String.valueOf(count) + ' ' 
+ String.valueOf(pagecount));
}
public int getPagecount(){
return pagecount;
}
public int getCount(){
return count;
}
public int getCurrentpage(){
return currentpage;
}
public void firstPage(){
currentpage = 1;
}
public void nextPage(){
currentpage++;
}
public void prevPage(){
currentpage--;
}
public void endPage(){
currentpage = pagecount;
}
public void setCurrentpage(int currentpage){
this.currentpage = currentpage;
}
public ArrayList<HashMap<String, String>> getList() {
if (currentpage == pagecount) {
return service.setAdapterListData((pagecount - 1) * pagesize, pagesize);
} else if (currentpage == 1){
return service.setAdapterListData(0, pagesize);
}else {
return service.setAdapterListData((currentpage - 1) * pagesize, pagesize);
}
}
}
在启动Activity文件中,OnCreate()代码如下:

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);            
     
        getDBFilesFromQQPath(); //获取DB文件
        setControls();  //动态设置按钮  
}
getDBFilesFromQQPath()从QQ的数据库目录复制数据库过来,然后用setControls()动态的创建按钮列表显示出已登陆过的QQ号,在getDBFilesFromQQPath()中,有这么一段代码需要注意:

List<String> strings = DBFiles.getDBPaths(); 
Log.i("DBFiles", strings.toString());
for (String str : strings){
   Log.i("DBFiles", str);
    if (str.length() == 0) continue;
    String permission="chmod 666 "+ str + '\n'; 
    RootUtils.RootCommand(permission);   //加上读写权限
}
这段代码执行了“chmod 666 xxx.db”将db文件设置成所有组与用户可读写,也就是它帮我解决了代码时遇到的一件囧烦事,我将QQ的db文件手动通过“adb pull xxx.db, adb push xxx.db”后可以通过Sqlite类访问它,可是使用代码复制过来的db文件在访问时始终返回“sqlite returned: error code=14”,网上查了下,都说是Sqlite打开失败了,有可能是文件正在被使用,我在代码中加了一行RootUtils.RootCommand("killall com.tencent.mobileqq\n");结束掉QQ的进程,依然无效,这让当时很是困惑,后来在抽完几支烟后静下心来,把手动push的db文件与复制的文件对比后发现,代码复制过来的db文件只是root用户才有读写权限,加上改权限的代码后程序快乐的运行起来了*^_^*

三个服务类大体上差不多,只是执行的SQL语句不同,在这里,只列一下QQFriendService代码:

public List<Object> queryList(int startIndex, int endIndex)
{
List<Object> friends = new ArrayList<Object>();
try
{
SQLiteDatabase data = db.getReadableDatabase();
StringBuilder sb = new StringBuilder(
"select group_name, name, uin from Friends,Groups where Friends.groupid=Groups.group_id order by group_id");
sb.append(" limit ");
sb.append(String.valueOf(startIndex));
sb.append(',');
sb.append(String.valueOf(endIndex));
Log.i("SQL", sb.toString());
Cursor cursor = data.rawQuery(sb.toString(), null);
while (cursor.moveToNext())
{
Log.i("SQL", cursor.getString(1));
friends.add(new QQFriend(cursor.getString(0), cursor
.getString(1), cursor.getString(2)));
}
} catch (Exception e)
{
e.printStackTrace();
}
return friends;
}
/**
 * 查询好友数目
 */
public long queryCount()
{
SQLiteDatabase data = db.getReadableDatabase();
Cursor cursor = data.rawQuery("select count(*) from Friends", null);
if (cursor.moveToNext()) {
return cursor.getLong(0);
}
return 0;
}
public ArrayList<HashMap<String, String>> setAdapterListData(int startIndex, int endIndex)
{
List<Object> objs;    
     ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
        try
{
objs = queryList(startIndex, endIndex);
for (Object obj : objs)
{
HashMap<String, String> map = new HashMap<String, String>();
QQFriend friend = (QQFriend) obj;
map.put("memberLevel", friend.getMemberLevel());
map.put("name", friend.getName());
map.put("qquin", friend.getqqUin());
list.add(map);
}
} catch (Exception e)
{
e.printStackTrace();
}
return list;
}
数据库操作存在的各种各样的异常太多了,在操作前都try,catch一下,有了exception就e.printStackTrace()一下,分页操作的代码限于篇幅就不帖了,大家可以自己参看程序完整代码。其它啥的就没什么好说了,具体就看完整的代码,最后上上效果图7与图8:

 

 


 

图 7

 


图 8

到这里文章就告一段落了,代码能够运行的原因只是因为手机被ROOT了。现在看了这篇文章后,亲害怕了吗?亲还会想要ROOT你的手机吗?

ROOT过的手机上可能安装了如“淘宝”、“支付宝”、“XX银行”之类的软件,然而目前用户根本毫无安全意识可言,不停安装各类趣味免费游戏、破解版软件、追求新鲜,却不知这背后隐藏着怎样可怕的猫腻,不过,真相只有一个,我们一起来发现!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息