伪造客户端IP
· 为什么要伪造IP?
· 伪造IP能干吗?
· 如何伪造?
上面这三个问题希望能帮你在这篇文章中找到答案。先来说如何伪造。
伪造IP的思路是通过修改 Header 来实现增加 XFF 字段
XFF字段在我之前的推送中有介绍过具体是什么含义跟作用,
那些伪造IP的软件都是什么原理
但是在那篇推送中没有公开源码,其实也是出于安全考虑。说到这里就要来回答伪造IP能干嘛的问题了。
伪造IP的作用只局限于想象力,简单的,能刷刷投票,
复杂的话,包括黑服务器,绕过IP限制抓取后端数据,或者模拟测试看服务端承压能力如何,都是可以的。
至于商业作用?伪造IP也能做到大部分人想到的歪心思。
伪造IP在Android移动端上分两种情况,下面分别介绍。
修改自己的app IP的地址
如果你想修改自己的app的IP地址的话方法很简单,
结合上面说过的 XFF 字段,你只需要在每个 HTTP 请求头上加上 X-Forwarded-For 字段就行。
如果你想修改的是其他app的IP地址…请往下看。
修改第三方 IP 地址
下面说的方法是用来修改那些我们动不了的HTTP请求逻辑的。
有人说只要是JAVA代码,就算被封装起来也可以用反射来Hook呀!
是的没错,但是如果要修改的是任意一个装在你手机上的app的IP地址,
比如把某个视频app的IP改到境外,看一些在境内看不到的视频内容,嗯哼。
工具
在正式开始侵入之前你需要了解 Xposed 框架。如果没了解过的话可以看我这个推送
教你三分钟写个Xposed模块
我们的原理也是把HTTP请求加上XFF字段,方法就是把目标app的所有HTTP请求都截下来,然后动手脚。
HttpUrlConnection->connect()
HttpUrlConnection->getInputStream()
上面这两个方法是所有HTTP请求的必经之路,但是有个问题需要注意,就是必须要在 connect() 发生之前去修改 Header,在 connect() 之后是修改不了的。所以要实现的方法是 beforeHookedMethod() 方法。
下面是hook connect()方法的代码,
XposedHelpers.findAndHookMethod("com.android.okhttp.internal.huc.HttpURLConnectionImpl", lpparam.classLoader, "connect", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
HttpURLConnection urlConn = (HttpURLConnection) param.thisObject;
XposedBridge.log("[beforeHookedMethod] connect url: " + urlConn.getURL());
if (urlConn != null) {
try {
urlConn.addRequestProperty("X-Forwarded-For", info.getIp());
} catch (IllegalStateException exp) {
XposedBridge.log("[beforeHookedMethod] handleHttpHook, exp: " + exp.getMessage());
}
}
super.beforeHookedMethod(param);
}
});
可以看到这里注入的对象是 HttpURLConnectionImpl,方法是 connect()。这里有个细节问题,各个厂商的FW可能有不同,在适配不同厂商的时候这里 hook的对象可能不太一样,需要看实际情况修改。
hook完 conect(),下面要hook getInputStream(),基本套路一样
XposedHelpers.findAndHookMethod("com.android.okhttp.internal.huc.HttpURLConnectionImpl", lpparam.classLoader, "getInputStream", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
HttpURLConnection urlConn = (HttpURLConnection) param.thisObject;
XposedBridge.log("[beforeHookedMethod] getInputStream url: " + urlConn.getURL());
if (urlConn != null) {
try {
urlConn.setRequestProperty("X-Forwarded-For", info.getIp());
} catch (IllegalStateException exp) {
XposedBridge.log("[afterHookedMethod] getInputStream, exp: " + exp.getMessage());
}
}
super.beforeHookedMethod(param);
}
});
验证方法
如果成功的注入上这两段代码而且在运行时没有崩溃的话,那么恭喜你,你可以用Fiddler或者Charles之列的工具抓包看Header是否已经带上这个字段。
完整代码
感谢看到这里的朋友,关于 Xposed 的完整代码附在下面,在注入上面两个方法之前还需要判断一下目标 app 是不是你想要的,否则 Xposed会 hook 掉所有的请求,这会导致一些不可控的问题。
public class MainHook implements IXposedHookLoadPackage{
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
XposedBridge.log("[handleLoadPackage] " + lpparam.packageName);
if(!lpparam.packageName.equals("Your Package Name")) {
return;
}
XposedBridge.log("[packageHoooked] " + lpparam.packageName);
handleHttpConnectHook(lpparam, info);
handleInputStreamHook(lpparam, info);
}
private void handleHttpConnectHook(XC_LoadPackage.LoadPackageParam lpparam, final DeviceInfo info) {
XposedBridge.log("[methodhook] handleHttpHook()");
XposedHelpers.findAndHookMethod("com.android.okhttp.internal.huc.HttpURLConnectionImpl", lpparam.classLoader, "connect", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
HttpURLConnection urlConn = (HttpURLConnection) param.thisObject;
XposedBridge.log("[beforeHookedMethod] connect url: " + urlConn.getURL());
if (urlConn != null) {
try {
urlConn.addRequestProperty("X-Forwarded-For", info.getIp());
urlConn.setRequestProperty("User-Agent", Const.HEADER_UA);
} catch (IllegalStateException exp) {
XposedBridge.log("[beforeHookedMethod] handleHttpHook, exp: " + exp.getMessage());
}
}
super.beforeHookedMethod(param);
}
});
}
private void handleInputStreamHook(XC_LoadPackage.LoadPackageParam lpparam, final DeviceInfo info) {
XposedHelpers.findAndHookMethod("com.android.okhttp.internal.huc.HttpURLConnectionImpl", lpparam.classLoader, "getInputStream", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
HttpURLConnection urlConn = (HttpURLConnection) param.thisObject;
XposedBridge.log("[beforeHookedMethod] getInputStream url: " + urlConn.getURL());
if (urlConn != null) {
try {
urlConn.setRequestProperty("X-Forwarded-For", info.getIp());
} catch (IllegalStateException exp) {
XposedBridge.log("[afterHookedMethod] getInputStream, exp: " + exp.getMessage());
}
}
super.beforeHookedMethod(param);
}
});
}
}
那些伪造IP的软件都是什么原理
很多人可能都有过这个念头,
如何伪装客户端IP?
还有那些投票刷票的工具是怎么个原理?
先复习下TCP
要明白伪装IP的原理,首先要回顾一下TCP的三次握手。
总所周知在链接初始化的阶段,
需要一次三次握手来建立链接,
之后客户端和服务端会依据初始的这个IP地址来通信。
从这个角度上来说,
想真正的伪装一个IP地址是不可能的。
因为即使从链路层把IP地址改了,
后续发出去的请求也回不来,
除非你只是想用来作为DDOS攻击,要不没什么意义。
所以要另辟蹊径。
代理转发规则
用过代理吧?
不管是VPN,还是局域网代理,
都是通过代理服务器转发客户端的消息到服务端。
这里就引出主角,一个在 Header里的字段
X-Forwarded-For,
很多地方简称 XFF。
XFF不是一个标准的HTTP协议,
但是它被广泛接受用作于标识代理转发的规则。
通常一个 XFF字段会像下面这样,
X-Forwarded-For: 127.0.0.1, IP2, ..., IP N
从左到右,
依次记录的是距离服务器距离远的代理服务器的IP。
通过这个字段,
服务端就能知道这个请求是通过哪些代理转发过来的了。
而有些服务端的bug在于,
他们不关心这个请求的原始地址,而只关心最后那个转发给他的 IP N的代理。
正常来说,
应该把三次握手的 IP 作为 Remote IP记录,
以这个为客户端的唯一 IP,这样才是准确的。
实现
说了那么多理论,
下面来看看如何使用这个 XFF字段。
首先我们用 postman 模拟一个 HTTP 请求,看看返回的字段里携带的客户端信息是怎样的。
这里随意找了个服务器发了个模拟请求,
下面的 device字段不仅包含了 IP 地址,也包括了经纬度。
然后我们再人为的添加上 XFF 字段,
看看 device信息变成了什么。
看到了吗,
现在已经变成了一个假的位置。
而且即使这个IP地址明显错误,
服务端也没用校验这个异常。
总结
今天介绍的这个伪造IP的方法,
只是希望做服务端的朋友知道这个bug的存在,
进而避免很多违规的刷单行为。
技术本身没有错,
但如果把技术用在歧途上就不对了。
教你三分钟写个Xposed模块
了解Android开发的同学可能听说过Xposed,它的强大可以说是只有你想不到的,没有它做不到的功能。
举个例子,
它可以修改系统的IMEI,手机号,甚至可以说可以让一台小米手机完全变成一台OPPO。
原理
要知道Xposed是怎么做到的,首先要知道它的原理。
它是通过Hook API,替换掉原本的实现逻辑或者数据,来实现自己想要的方法。
看一段代码
XposedHelpers.findAndHookMethod(TelephonyManager.class.getName(), lpparam.classLoader, "getDeviceId", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("[afterHookedMethod] IMEI");
param.setResult("12345");
super.afterHookedMethod(param);
}
});
上面这段代码,把系统 TelephonyManager的 getDeviceId方法所返回的值,改成了 12345,
这样我们指定的 app调用 getDeviceId的话,就只能拿到 12345了。
实现
实现很简单,我们只需要定义一个入口类,比如叫 MainHook.java,然后让它实现 IXposedHookLoadPackage接口就可以。
public class MainHook implements IXposedHookLoadPackage{
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
....
}
回调方法 handleLoadPackage会在系统每个app启动的时候回调一下,这时候就给了我们Hook的机会。
我们只需要判断 lpparam是否是我们的目标 app,就可以替换任何我们想要的方法。
一个完整的 Hook入口类demo差不多是这样的
public class MainHook implements IXposedHookLoadPackage{
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if(!lpparam.packageName.equals("target.package")){
return;
}
XposedHelpers.findAndHookMethod(TelephonyManager.class.getName(), lpparam.classLoader, "getDeviceId", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("[afterHookedMethod] IMEI");
param.setResult("12345");
super.afterHookedMethod(param);
}
});
}
上面这个demo,就实现了Hook target.package 这个包名的app的 getDeviceId方法,从而返回我们设定的 12345.