背景
由于iOS端整体安全水平的提高,被破解的可能极大,安全攻防是一个持续对抗过程。一般来说可以采用如下几点保护自己的App:
- 底层防护,包括反调试,反注入,反跟踪;指令混淆(如Ollvm/栈模拟),花指令;动态指令,系统调用;各种黑科技,这些防护适合在最底层语言(C/Asm)做
- 上层防护,异常处理,多线程回调,多进程通信;高级C++库(Boost);复杂虚函数类;多语言混合编译,解释器(Lua/Js),这些适合高级语言做,因为其库函数复杂,不够熟悉则无法逆向
- 网络防护,包括IPv6,http/2,http/3,websocket;Httplib/LibCurl等网络库;哈希/对称/非对称加解密算法库(魔改);防抓包,防重放,CDN防火墙;
- 数据防护,包括算法库(如魔改Aes,MD5等);序列化库(Protobuf/Jsoncpp等);文件篡改检测,包篡改检测,签名检测;字符串加密,调试信息,RTTI
- 核心逻辑防护,敏感数据转为Web服务,核心数据和逻辑Web化且须计算,如此攻击者无法获取固定的数据,因为破解只能针对本地二进制去做
现在各个语言都飞速发展,破解者同时精通所有语言是不可能的,所以如果能结合多语言保护软件,利用各个软件发展多年积累的时间优势对抗攻击者,让其陷入代码的海洋,无疑是最佳策略.在Python领域Nuitka是个不错的选择,用它可开发出三端(Win/Linux/Mac)的桌面端App.而Golang则可编译出后端/移动端/桌面端软件,因此笔者今天对Golang编译模块给iOS使用做了初步研究.
和以前一样本人测试环境为MacOS
安装配置golang
brew install golang
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=on
# 安装gomobile
go install golang.org/x/mobile/cmd/gomobile@latest
echo "export PATH=\$(go env GOPATH)/bin:\$PATH" >> ~/.zshrc
编译可执行文件
testgo.go
package main
import "fmt"
func main() {
fmt.Println("hello1")
}
testgo.plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>platform-application</key>
<true/>
</dict>
</plist>
env GOOS=darwin GOARCH=arm64 go build -ldflags '-s -w' -tags=ios testgo.go
ldid -Stestgo.plist testgo
#在设备上执行
注意: ldflags为去除dwarf信息,不加该项编译执行时会报如下错误:
dyld: malformed mach-o image: segment load command __DWARF filesize (0x7AB3C) is larger than vmsize (0x0)
Abort trap: 6
编译framework
testgolib.go
package testgolib
import "C"
func AddInt(a, b int) int {
return a + b
}
func AddStr(a, b string) string {
return a + b
}
main.mm
#import "Testgolib/Testgolib.h"
int main (int argc, const char * argv[]) {
NSLog(@"r1=%ld r2=%@", TestgolibAddInt(1, 2), TestgolibAddStr(@"1", @"2"));
return 0;
}
go mod init testgolib
go get golang.org/x/mobile/cmd/gobind
gomobile bind -target ios -o testgolib.xcframework testgolib
注意: 此方法需要xcode-select设置为正确的Xcode版本,笔者使用XCode12
注意: gomobile要求包名不能是main且不能有main函数,需要导出的函数首字母须大写
编译静态库
gomobile是简化编译iOS/Android的工具链,但本人今天研究之下发现其在Mac下只能产生动态库, 因此又研究了一下如何用Golang编译iOS静态库
testgolib.go
package main
import "C"
/*
int testfunc();
*/
//export AddInt
func AddInt(a, b int) int {
return a + b
}
//export AddStr
func AddStr(a, b string) *C.char { // cgo不支持返回go指针
c := a + b
return C.CString(c)
}
//export TestFunc
func TestFunc() C.int {
return C.testfunc()
}
func main() {
}
main.mm
#include <iostream>
#include <string>
extern "C" {
#include "testgolib.h"
}
static GoString stdstr_to_gostr(const std::string& s) {
return {s.c_str(), (ptrdiff_t)s.length()};
}
int testfunc() {
return 123;
}
int main (int argc, const char * argv[]) {
int r1 = (int)AddInt(1,2);
std::string r2 = AddStr(stdstr_to_gostr("1"), stdstr_to_gostr("2"));
std::cout << "r1=" << r1 << " r2=" << r2 << std::endl; // C++调用Go
std::cout << "r3=" << (int)TestFunc() << std::endl; // Go调用C
return 0;
}
export GOROOT=$(go env GOROOT)
# CGO_ENABLED=1 GOOS=ios GOARCH=arm64 CC="xcrun -sdk iphoneos clang" CXX="xcrun -sdk iphoneos clang" \
CGO_ENABLED=1 GOOS=ios GOARCH=arm64 CC="$GOROOT/misc/ios/clangwrap.sh" CXX="$GOROOT/misc/ios/clangwrap.sh" \
CGO_CFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=9.0 -fembed-bitcode -arch arm64" \
CGO_CXXFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=9.0 -fembed-bitcode -arch arm64" \
CGO_LDFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=9.0 -fembed-bitcode -arch arm64" \
go build -buildmode=c-archive -o testgolib.a testgolib.go
注意: 正常情况cgo会生成.a和.h文件
注意: cgo导出函数必须指定注释"//export",需要有main函数且是main包
注意: cgo不支持返回string类型,所以转而用C.char,否则报错
goroutine 17 [running, locked to thread]:
panic({0x10052a6c0, 0x13001a290})
/usr/local/Cellar/go/1.19.3/libexec/src/runtime/panic.go:987 +0x3c0
runtime.cgoCheckArg(0x100527a80, 0x13001a280, 0x10?, 0x0, {0x10050295b, 0x19})
/usr/local/Cellar/go/1.19.3/libexec/src/runtime/cgocall.go:524 +0x308
...
编译release
只需要指定ldflags
go build -ldflags "-s -w"