背景
app本地网页文件一般情况下会选择对js进行混淆加密,但混淆加密只是对js文件中的内容进行的混淆,在一定程度上还是可以还原出来。现在介绍另外一种加密方式,虽说此种加密方式也不完全不能还原出来,但可以尽量减少性能影响的情况下提高解密难度。
此种方式Android和iOS两种平台都可使用,只存在少量差别,总体思路一样。
加密方式详解
重定向js文件
常规的混淆加密方式只是对字符串进行了混淆,但如果在浏览器中调试仍然可以看清网页执行的来龙去脉。如果我们能够捕获网页所需要加载的js文件,并将其重定向到另外一个文件,这样就可以给破解者增加了一定的难度。
HybridNSURLProtocol
一个基于WKWebView的hybirde的容器。能拦截所有WKWKWebView的的css,js,png等网络请求的demo NSURLProtocol 子类,就可以对 app 内所有的网络请求进行:
[NSURLProtocol registerClass:[HybridNSURLProtocol class]];
可是在 WKWebView 中的请求却完全不遵从这一规则,只是象征性+ (BOOL) canInitWithRequest:(NSURLRequest *)request 方法,之后的整个请求流程似乎就与 NSURLProtocol 完全无关了。
使我WKWebView 的一度认为请求不遵守NSURLProtocol协议,所以不走 NSURLProtocol。这个也是很苦扰我的问题。导致我们hybird的容器1.0也是是用UIWebVIew实现的。
但在苹果放在gittub的CustomHTTPProtocol,明显感觉到WKWebview的也是遵守NSURLProtocol,要不也不会走+ (BOOL)canInitWithRequest:(NSURLRequest *)request;后来一个每天看博客和gittub的习惯帮助了我,找到一个大神的不久前开源库。
使用了WKBrowsingContextController和registerSchemeForCustomProtocol。 通过反射的方式拿到了私有的 class/selector。通过kvc取到browsingContextController。通过把注册把 http 和 https 请求交给 NSURLProtocol 处理
[NSURLProtocol wk_registerScheme:@"http"];
[NSURLProtocol wk_registerScheme:@"https"];
下面直接上源代码吧
//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。在SDWebImage读取内存的缓存也用到这个声明。
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
注册后,客户端所有请求走+ (BOOL)canInitWithRequest:(NSURLRequest *)request。下面是打印的请求的log
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString);
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:KHybridNSURLProtocolHKey inRequest:request])
return NO;
return YES;
}
return NO;
}
request的重写定向,request的重写定向,替换百度知道的log
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString);
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:KHybridNSURLProtocolHKey inRequest:request])
return NO;
return YES;
}
return NO;
}
这里最好加上缓存判断,加载本地离线文件, 这个直接简单的例子。
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//给我们处理过的请求设置一个标识符, 防止无限循环,
[NSURLProtocol setProperty:@YES forKey:KHybridNSURLProtocolHKey inRequest:mutableReqeust];
//这里最好加上缓存判断,加载本地离线文件, 这个直接简单的例子。
if ([mutableReqeust.URL.absoluteString isEqualToString:sourIconUrl])
{
NSData* data = UIImagePNGRepresentation([UIImage imageNamed:@"medlinker"]);
NSURLResponse* response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"image/png" expectedContentLength:data.length textEncodingName:nil];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
}
else
{
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.task = [session dataTaskWithRequest:self.request];
[self.task resume];
}
}
以上重定向内容转自网络。
现在回到我们的加密主题上来,根据上述方法得知重定向js文件,在一开始注册file字段,然后在canInitWithRequest函数中捕获file的request,最后在canonicalRequestForRequest函数中进行处理,即可重定向文件。
文件加密
处理完重定向以后,下面我们把重点放在文件的加密上。由于传统的混淆方式,js文件仍然以明文的方式存在,且破解难度较小,这是因为浏览器的限制,只能加载能够识别的明文。而现在我们可以把js处理成任意加密方式,只要在重定向的时候能找到相应的文件并且解析出来。以下提供一种可以成功加密解密方式。
- 将js文件内容进行base64编码
- 用ase之类的加密方式把base64编码后的内容进行加密,此时需要读取到内存中,然后以二进制的方式写入到磁盘,此时文件已经完成加密。外部人员打开只会显示此文件是一个二进制文件,从而有效地对文件进行了加密
- 读取js文件时(默认读取的是已经利用上述步骤加密好的文件),首先进行解密得到base64字符串,然后[NSURL URLWithString:[NSString stringWithFormat:@”data:text/html;charset=UTF-8;base64,%@”,strEncode]]方式获取NSURL,这样就能成功的被浏览器识别。
接受启发的作者的github
github:Yeatse CC
github:LiuShuoyu
苹果开发者文档:apple