背景
之前使用过一个gif的手机软件挺不错的,就是有广告,由于习惯了白嫖
,就想起了移除广告的想法,经过一顿操作之后,发现了白嫖:真香。顺便分享下如何做简单的app逆向,当然这只是简单的入门
,大佬可以关了。
工具和样本
最新连接:链接: https://pan.baidu.com/s/1u0oMoZG0TLIRJTryQDnJaQ 提取码: bm6a 复制这段内容后打开百度网盘手机App,操作更方便哦
如何去广告?
因为app比较特殊,gif制作是本地完成的,不需要联网,这里最最简单的方式就是:拔网线
网络没有了就不会有广告显示了,且不影响本地功能。
ok,先看看我们有广告的情况。
可以看到,开屏,进入某个功能区的时候显示广告了。我们就开始操作断网!
如何断网?
大家都是写过Android软件的,断网就是把网络权限删掉
!是不是很简单啊。
既然如何那我们就打开逆向工具,把网络权限删掉,再回编译试试。
打开工具,把apk拖进去。
失败原因就是apktool版本太老了,无法对apk进行反编译。我们去下载最新版本。 官方: https://ibotpeaches.github.io/Apktool/install/ 文件列表: https://bitbucket.org/iBotPeaches/apktool/downloads/ 我下载的最新版2.5.0,上面分享的链接已经有了。 下载回来之后放到一个没有中文路径的目录下。开始配置killer的反编译工具。
这个时候反编译完成了。是不是很简单,只需要拖拖点点,这是吾爱大佬的工具虽然很多年没更新了但是依然好使,逆向小白的最爱工具了。
删除网络权限
对于这个操作,大家都很熟悉,去Android的AndroidManifest.xml
中找到网络权限
<uses-permission android:name="android.permission.INTERNET"/>
把这一行删掉就行了。然后把回编译apk安装,收工。
安装如果失败了,就是正常的,因为我们手机上面已经有了一个同包名的app,这个killer打包出来的apk是用我们电脑默认的签名打的,之前没有改动的apk是人家作者打出来,签名不同是无法覆盖安装(除非你有幸运破解器
)。
删除手机上面那个,再安装我们删除了网络权限的就行了。
看了下手机,熟悉页面啊,这就是我们平时的crash
。就是说App被删除网络权限后打开就奔溃了。
奔溃定位
打开Android studio,打开一个空的工程,链接好手机,打开logcat
看log。再打开app让它奔溃一次,过滤奔溃信息。在logcat中不过滤等级,搜索beginning of crash
,或者过滤error,或者过滤包名
这个奔溃就是当前去广告的奔溃了。
分析奔溃
奔溃内容重点
java.lang.SecurityException: Permission denied (missing INTERNET permission?)
很多朋友不知道如何得到奔溃信息,或者是看到了奔溃信息一堆不知道那个才是重点,提问的时候就说:我的App奔溃了.............. 这个不管是逆向还是开发app都需要会看错误。
java.lang.SecurityException: Permission denied (missing INTERNET permission?)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:151)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:175)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:141)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:83)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:174)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:407)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:538)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:26)
at com.bytedance.embedapplog.c.a.a(SourceFile:53)
at com.bytedance.embedapplog.c.a.b(SourceFile:13)
at com.bytedance.embedapplog.a.b.d(SourceFile:5)
at com.bytedance.embedapplog.a.c.h(SourceFile:4)
at com.bytedance.embedapplog.a.e.handleMessage(SourceFile:10)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:207)
at android.os.HandlerThread.run(HandlerThread.java:65)
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
at libcore.io.Linux.android_getaddrinfo(Native Method)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:172)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:137)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:175)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:141)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:83)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:174)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:407)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:538)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:26)
at com.bytedance.embedapplog.c.a.a(SourceFile:53)
at com.bytedance.embedapplog.c.a.b(SourceFile:13)
at com.bytedance.embedapplog.a.b.d(SourceFile:5)
at com.bytedance.embedapplog.a.c.h(SourceFile:4)
at com.bytedance.embedapplog.a.e.handleMessage(SourceFile:10)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:207)
at android.os.HandlerThread.run(HandlerThread.java:65)
Caused by: android.system.ErrnoException: android_getaddrinfo failed: EACCES (Permission denied)
at libcore.io.Linux.android_getaddrinfo(Native Method)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:172)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:137)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:175)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:141)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:83)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:174)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:407)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:538)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:26)
at com.bytedance.embedapplog.c.a.a(SourceFile:53)
at com.bytedance.embedapplog.c.a.b(SourceFile:13)
at com.bytedance.embedapplog.a.b.d(SourceFile:5)
at com.bytedance.embedapplog.a.c.h(SourceFile:4)
at com.bytedance.embedapplog.a.e.handleMessage(SourceFile:10)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:207)
at android.os.HandlerThread.run(HandlerThread.java:65)
Permission denied (missing INTERNET permission?)
我们本来想配置文件中移除网络权限,就断开广告的获取了,但是现在因为无网络权限直接SecurityException
,我们看看是什么原因抛出的
前面3行是获取ipv6
,通过host name
获取,继续往下面走。 Dns:39
行。开发APP的都比较熟悉,使用okhttp
框架做网络请求,既然这样,我进入okhttp
的代码看看这个地方发生了什么。
打开jadx
工具,另一个直接反编译看java代码的工具,中看看Dns
:39行做了什么事情! github搜索Jadx下载最新版本即可。(网盘链接有分享) 打开jadx,打开apk,点击搜索按钮,等等反编译完成建立索引。输入Dns
,把类名,方法名,变量,代码全部打钩。等待查找完成。
第一个结果就是了。点击它进入代码
还记得我们奔溃的堆栈吗
java.lang.SecurityException: Permission denied (missing INTERNET permission?)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:151)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:175)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:141)
lookup
方法调用jdk的getAllByName
->lookupAllHostAddr
->lookupHostByName
jdk的部分我们已经无法干预了,在改代码的角度是无法干预的,这个地方可以通过hook,直接屏蔽getAllByName
转nop,不过需要xposed环境了。这里不适用这个方式。既然jdk我们不处理,那就处理okhttp
的Dns
类
class Dns implements AbstractC0370z {
Dns() {
}
@Override // okhttp3.AbstractC0370z
public List<InetAddress> lookup(String str) throws UnknownHostException {
if (str != null) {
try {
return Arrays.asList(InetAddress.getAllByName(str));
} catch (NullPointerException e2) {
UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour for dns lookup of " + str);
unknownHostException.initCause(e2);
throw unknownHostException;
}
} else {
throw new UnknownHostException("hostname == null");
}
}
}
我们回到lookup
,这里出现了一个判断,如果str!=null
就触发我们奔溃的堆栈了,那么长得帅网友就瞬间明白了,让这个判断反转变str==null
不就好了吗?既然这样又让我想起了hook
方法了,直接把lookup
入参设置null
,同样达到这个效果哦。但是我们这里是改代码,关于hook
替换入参,大家可以试试,很简单。
接下来就是修改这个if
的判断了,我们回到killer中,搜索Dns
结果就是有很多条目,我们对着堆栈的包名来分析。
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
发现4个文件,打开4个文件,然后对比我们的伪java代码看看特性
2个标识:lookup
方法,还有就是一个字符串Broken system behaviour for dns lookup of
直接搜索这个字符串,好家伙,在4个文件中有一个就是了,我们分析这个文件的代码。
这个是smali
,搞逆向的基本都熟悉,就好像你天天开发看java一样,写多,看多了,自然就看得懂。
Android虚拟机Dalvik并不是执行java虚拟机JVM编译后生成的class文件,而是执行再重新整>打包后生成的dex文件,dex文件反编译之后就是smali代码,可以说,smali语言是Dalvik的汇编语言 我以前通过网上资料学习过一段时间这个语言,能看明白一点点,我假设这个你已经了解过这种语言,就算不懂,语言相通特性,能大概看明白就行了。 遇到不懂的就去查。
.class Lokhttp3/y;
.super Ljava/lang/Object;
## 这个是源文件名字,他就是我们找的Dns类
.source "Dns.java"
# interfaces 实现某个接口
.implements Lokhttp3/z;
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lokhttp3/z;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = null
.end annotation
# direct methods
.method constructor <init>()V
.locals 0
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods 这个是我们关系的lookup方法,我们要改的代码就是这个方法体的
.method public lookup(Ljava/lang/String;)Ljava/util/List;
.locals 4
.annotation system Ldalvik/annotation/Signature;
value = {
"(",
"Ljava/lang/String;",
")",
"Ljava/util/List<",
"Ljava/net/InetAddress;",
">;"
}
.end annotation
.annotation system Ldalvik/annotation/Throws;
value = {
Ljava/net/UnknownHostException;
}
.end annotation
# lookup的方法开始,符合条件往下面的代码走,不符合条件,走cond_0,我们需要修改不符合条件
# 我们需要的是if(str!=null) 走UnknownHostException,不走return Arrays.asList(InetAddress.getAllByName(str));
# 源if-eqz ,改if-nez意图:如果if(str!=null)执行UnknownHostException
if-nez p1, :cond_0
.line 1
:try_start_0
invoke-static {p1}, Ljava/net/InetAddress;->getAllByName(Ljava/lang/String;)[Ljava/net/InetAddress;
move-result-object v0
invoke-static {v0}, Ljava/util/Arrays;->asList([Ljava/lang/Object;)Ljava/util/List;
move-result-object p1
:try_end_0
.catch Ljava/lang/NullPointerException; {:try_start_0 .. :try_end_0} :catch_0
return-object p1
:catch_0
move-exception v0
.line 2
new-instance v1, Ljava/net/UnknownHostException;
new-instance v2, Ljava/lang/StringBuilder;
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
const-string v3, "Broken system behaviour for dns lookup of "
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v2, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object p1
invoke-direct {v1, p1}, Ljava/net/UnknownHostException;-><init>(Ljava/lang/String;)V
.line 3
invoke-virtual {v1, v0}, Ljava/net/UnknownHostException;->initCause(Ljava/lang/Throwable;)Ljava/lang/Throwable;
.line 4
throw v1
.line 5
:cond_0
new-instance p1, Ljava/net/UnknownHostException;
const-string v0, "hostname == null"
invoke-direct {p1, v0}, Ljava/net/UnknownHostException;-><init>(Ljava/lang/String;)V
throw p1
.end method
修改small
要修改代码,就得遵循语法,至于这个,就得靠自己去学习了,如果修改了语法错误,在运行时才保错的,ide无法提示过多了,通常语法错误都是verify关键词,奔溃中搜索verify就行。如果是逻辑错误,那就自己的问题了。
修改完成之后要保存代码,然后回编译成apk
安装完成启动App成功了,体验一番之后全部功能正常。这个广告算是移除了。 最后我们看看修改完成之后的java伪代码。把自己修改apk再次拉进jadx中看看Dns的代码
总结
1、使用Android killer工具进行代码修改(吾爱官方中killer存在bug,反编译完成后卡主无法完成,网盘的是有个大神修复过的) 2、使用jadx进行代码预览分析定位 3、修改smali代码进行功能实现 4、Android studio的loacat工具方便查看奔溃日志,分析奔溃日志 5、熟悉反编译工具 6、熟悉app原生开发(熟悉java语言)和常用组件,当你熟悉正向开发,很多流程你能猜出来,改逻辑自然也容易
这是一个比较简单的例子,大佬们如果有更雕x的工具和技巧,欢迎评论区交流。
不提供成品~