找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 528|回复: 5

【Golang】ObjectiveC/C++调用Golang之初试

[复制链接]

307

主题

228

回帖

7267

积分

用户组: 真·技术宅

UID
2
精华
76
威望
291 点
宅币
5517 个
贡献
253 次
宅之契约
0 份
在线时间
937 小时
注册时间
2014-1-25
发表于 2023-3-22 01:18:25 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 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

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"
回复

使用道具 举报

1109

主题

1648

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24141 个
贡献
46222 次
宅之契约
0 份
在线时间
2291 小时
注册时间
2014-1-26
发表于 2023-3-26 16:34:58 | 显示全部楼层
golang 会在它的 API 接口界面被调用的那一瞬间发生 GC 导致指针被改动嘛?
回复 赞! 靠!

使用道具 举报

307

主题

228

回帖

7267

积分

用户组: 真·技术宅

UID
2
精华
76
威望
291 点
宅币
5517 个
贡献
253 次
宅之契约
0 份
在线时间
937 小时
注册时间
2014-1-25
 楼主| 发表于 2023-3-26 17:33:46 | 显示全部楼层
本帖最后由 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问题
回复 赞! 靠!

使用道具 举报

1109

主题

1648

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24141 个
贡献
46222 次
宅之契约
0 份
在线时间
2291 小时
注册时间
2014-1-26
发表于 2023-3-26 22:14:51 | 显示全部楼层
lichao 发表于 2023-3-26 17:33
我的理解是,纯go的话对象gc肯定是自动的,毕竟是高级语言了,当然系统资源还需要手动释放.
返回指针如果 ...

我觉得正常情况都是不能让 go 返回指针给 C 的,任何带 GC 的语言不能直接把自己池子里的东西的地址提供给别人,不然难以跟踪池子里的东西的占用情况。

或者,go 的 GC 也可以在扫描内存和线程上下文的时候顺着栈和寄存器一路摸到 C 的函数调用内部,判断一个东西像不像指着自己池子的 pointer,然后对其进行处理。可能会误伤到数值上正好也是池子地址范围内的 size_t 变量。

回复 赞! 靠!

使用道具 举报

307

主题

228

回帖

7267

积分

用户组: 真·技术宅

UID
2
精华
76
威望
291 点
宅币
5517 个
贡献
253 次
宅之契约
0 份
在线时间
937 小时
注册时间
2014-1-25
 楼主| 发表于 2023-3-26 23:20:51 | 显示全部楼层
本帖最后由 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其实差不多

回复 赞! 靠!

使用道具 举报

0

主题

9

回帖

13

积分

用户组: 初·技术宅

UID
5120
精华
0
威望
2 点
宅币
0 个
贡献
0 次
宅之契约
0 份
在线时间
1 小时
注册时间
2019-7-9
发表于 2023-4-19 23:59:13 | 显示全部楼层
非常好,谢谢
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|申请友链||Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-3-1 05:54 , Processed in 0.041131 second(s), 32 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表