【Golang】ObjectiveC/C++调用Golang之初试
本帖最后由 lichao 于 2023-4-14 15:29 编辑## 背景
  由于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
```bash
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
```Golang
package main
import "fmt"
func main() {
fmt.Println("hello1")
}
```
testgo.plist
```xml
<!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>
```
```bash
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
```Golang
package testgolib
import "C"
func AddInt(a, b int) int {
return a + b
}
func AddStr(a, b string) string {
return a + b
}
```
main.mm
```Objective-C
#import "Testgolib/Testgolib.h"
int main (int argc, const char * argv[]) {
NSLog(@"r1=%ld r2=%@", TestgolibAddInt(1, 2), TestgolibAddStr(@"1", @"2"));
return 0;
}
```
```bash
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
```Golang
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
```Objective-C
#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 :
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
```bash
go build -ldflags "-s -w"
```
golang 会在它的 API 接口界面被调用的那一瞬间发生 GC 导致指针被改动嘛? 本帖最后由 lichao 于 2023-3-26 17:55 编辑
0xAA55 发表于 2023-3-26 16:34
golang 会在它的 API 接口界面被调用的那一瞬间发生 GC 导致指针被改动嘛?
我的理解是,纯go的话对象gc肯定是自动的,毕竟是高级语言了,当然系统资源还需要手动释放.
返回指针如果是go调用c的api创建的话,还需要在c里释放,go不做干预,(本文只是简单测试所以忽略了这一步)
go返回c指针应是按值传递,所以其值不会变,而指针指的buffer,由于go未做干预也不变
至于gomobile则是基于cgo的,他接口返回对象有自己wrapper(包括Go层和OC层的)处理gc和类型转换,所以不用操心gc问题
lichao 发表于 2023-3-26 17:33
我的理解是,纯go的话对象gc肯定是自动的,毕竟是高级语言了,当然系统资源还需要手动释放.
返回指针如果 ...
我觉得正常情况都是不能让 go 返回指针给 C 的,任何带 GC 的语言不能直接把自己池子里的东西的地址提供给别人,不然难以跟踪池子里的东西的占用情况。
或者,go 的 GC 也可以在扫描内存和线程上下文的时候顺着栈和寄存器一路摸到 C 的函数调用内部,判断一个东西像不像指着自己池子的 pointer,然后对其进行处理。可能会误伤到数值上正好也是池子地址范围内的 size_t 变量。
本帖最后由 lichao 于 2023-3-27 00:05 编辑
0xAA55 发表于 2023-3-26 22:14
我觉得正常情况都是不能让 go 返回指针给 C 的,任何带 GC 的语言不能直接把自己池子里的东西的地址提供 ...
  文中的方式是混合编译,go可以直接调用c函数,他这里C.CString就是调用c的malloc创建了c字符串,这个前缀"C."就已经是调用c而不是调用go的内存分配,本质和“加载一个c的动态库,然后这动态库调用malloc”并没区别。如果这样能存在地址冲突,那么这语言存在严重bug。如果这函数返回了一个go对象指针给c用,那么这问题就大了,也不应该那么写,那种纯黑客玩法
  这种不同语言之间模块调用,应该是哪个语言分配的就谁来释放,即便能交叉释放也不该那么做。虽然go也能调用c释放,但是由于要返回,此时释放了返回的buf就有问题(stackoverflow就有一个这种错误回答,提问者那么改了以后得到了随机字符串,咱搞过c所以这种问题看一下就知道怎么做是对的,只会高级语言就没这种优势了)。作为生命周期来讲,此时应该是给返回的父函数处理。c++里也是这样,c++返回一个对象,也是在父函数执行析构和释放的;而c++临时对象作为参数传入,则是在子函数中析构和释放;栈对象还是本函数释放。
  实现返回字符串这个功能,除了返回c指针,还可以将buf对应的c指针传参,然后在golang里填充buf,这样不涉及动态分配,但是有点脱裤子放屁的意思。因为语言之间的调用,一定会涉及到复杂数据,总不可能只传int,最少要能支持传一段不定长的buf,这就得涉及指针和动态分配,这是免不了的。你想一下如果在ctypes里返回一个c的字符串怎么做呢,python也不会管c的内存分配啊。这个cgo和ctype其实差不多
非常好,谢谢
页:
[1]