iOS本地js文件加密

背景

  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;
}

Aaron Swartz

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

最近的文章

网页在iOS12键盘隐藏时视图发生偏移

背景  在iOS系统中会经常嵌入网页,在键盘输入的时候为了不遮挡键盘为使视图居中显示。但在此时在不同版本系统下或者使用不同的WebView(UIWebView或者WKWebView),会造成一些视图的偏移问题,以下介绍的是其中一个问题。问题描述  在iOS12系统下,使用WKWebView,并把该WKWebView的scrollview的contentInsetAdjustmentBehavior设置为UIScrollViewContentInsetAdjustmentNever。这一操作...…

继续阅读
更早的文章

navigationBar的隐藏

通用方法  一般情况下使用setNavigationBarHidden来隐藏导航栏,但在多个ViewController之间切换时来显示隐藏导航栏,会出现闪动的情况,以下来介绍另外一种方式来隐藏。- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;setTranslucent设置透明———–  函数解释如下@property(nonatomic, assign, getter=isTranslucent) ...…

继续阅读