444 lines
13 KiB
Go
444 lines
13 KiB
Go
package inject
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Platform 平台类型
|
|
type Platform string
|
|
|
|
const (
|
|
PlatformAndroid Platform = "android"
|
|
PlatformWindows Platform = "windows"
|
|
PlatformLinux Platform = "linux"
|
|
PlatformIOS Platform = "ios"
|
|
)
|
|
|
|
// Arch 架构类型
|
|
type Arch string
|
|
|
|
const (
|
|
ArchX86 Arch = "x86"
|
|
ArchX64 Arch = "x64"
|
|
ArchARM Arch = "arm"
|
|
ArchARM64 Arch = "arm64"
|
|
ArchUniversal Arch = "universal"
|
|
)
|
|
|
|
// InjectConfig 注入配置
|
|
type InjectConfig struct {
|
|
Platform Platform `json:"platform"`
|
|
Arch Arch `json:"arch"`
|
|
TemplateName string `json:"template_name"`
|
|
ShowVerification bool `json:"show_verification"`
|
|
ShowAd bool `json:"show_ad"`
|
|
AdURL string `json:"ad_url"`
|
|
CountdownSeconds int `json:"countdown_seconds"`
|
|
AppName string `json:"app_name"`
|
|
VerifyURL string `json:"verify_url"`
|
|
AppKey string `json:"app_key"`
|
|
}
|
|
|
|
// InjectResult 注入结果
|
|
type InjectResult struct {
|
|
Success bool `json:"success"`
|
|
ResultFile string `json:"result_file"`
|
|
ResultSize int64 `json:"result_size"`
|
|
Error string `json:"error"`
|
|
ProcessTime int64 `json:"process_time"` // 处理耗时(毫秒)
|
|
}
|
|
|
|
// InjectService 注入服务
|
|
type InjectService struct {
|
|
ApktoolPath string
|
|
KeystorePath string
|
|
KeystorePass string
|
|
KeystoreAlias string
|
|
KeyPass string
|
|
ApksignerPath string
|
|
WorkDir string
|
|
}
|
|
|
|
// NewInjectService 创建注入服务
|
|
func NewInjectService(workDir string) *InjectService {
|
|
// 确保 workDir 是绝对路径
|
|
absWorkDir, err := filepath.Abs(workDir)
|
|
if err != nil {
|
|
absWorkDir = workDir
|
|
}
|
|
|
|
return &InjectService{
|
|
WorkDir: absWorkDir,
|
|
ApktoolPath: getEnvOrDefault("APKTOOL_PATH", "apktool"),
|
|
KeystorePath: getEnvOrDefault("KEYSTORE_PATH", ""),
|
|
KeystorePass: getEnvOrDefault("KEYSTORE_PASS", "123456"),
|
|
KeystoreAlias: getEnvOrDefault("KEYSTORE_ALIAS", "mykey"),
|
|
KeyPass: getEnvOrDefault("KEY_PASS", "123456"),
|
|
ApksignerPath: getEnvOrDefault("APKSIGNER_PATH", "apksigner"),
|
|
}
|
|
}
|
|
|
|
func getEnvOrDefault(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// Inject 执行注入
|
|
func (s *InjectService) Inject(inputFile string, config InjectConfig) (*InjectResult, error) {
|
|
startTime := time.Now()
|
|
|
|
// 创建工作目录
|
|
taskID := fmt.Sprintf("inject_%d", time.Now().UnixNano())
|
|
taskDir := filepath.Join(s.WorkDir, taskID)
|
|
if err := os.MkdirAll(taskDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("创建工作目录失败: %v", err)
|
|
}
|
|
defer os.RemoveAll(taskDir) // 清理工作目录
|
|
|
|
var result *InjectResult
|
|
var err error
|
|
|
|
switch config.Platform {
|
|
case PlatformAndroid:
|
|
result, err = s.injectAndroid(inputFile, config, taskDir)
|
|
case PlatformWindows:
|
|
result, err = s.injectWindows(inputFile, config, taskDir)
|
|
case PlatformLinux:
|
|
result, err = s.injectLinux(inputFile, config, taskDir)
|
|
case PlatformIOS:
|
|
result, err = s.injectIOS(inputFile, config, taskDir)
|
|
default:
|
|
err = fmt.Errorf("不支持的平台: %s", config.Platform)
|
|
}
|
|
|
|
if err != nil {
|
|
return &InjectResult{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
ProcessTime: time.Since(startTime).Milliseconds(),
|
|
}, err
|
|
}
|
|
|
|
result.ProcessTime = time.Since(startTime).Milliseconds()
|
|
return result, nil
|
|
}
|
|
|
|
// injectAndroid Android APK 注入
|
|
func (s *InjectService) injectAndroid(inputFile string, config InjectConfig, taskDir string) (*InjectResult, error) {
|
|
log.Printf("[INFO] 开始 Android APK 注入: %s", inputFile)
|
|
|
|
// 1. 反编译 APK
|
|
decompileDir := filepath.Join(taskDir, "decompiled")
|
|
if err := s.decompileAPK(inputFile, decompileDir); err != nil {
|
|
return nil, fmt.Errorf("反编译失败: %v", err)
|
|
}
|
|
|
|
// 2. 复制注入模板的 smali 文件
|
|
templateDir := filepath.Join("inject", "android")
|
|
if err := s.mergeSmali(templateDir, decompileDir); err != nil {
|
|
return nil, fmt.Errorf("合并 smali 失败: %v", err)
|
|
}
|
|
|
|
// 3. 配置注入参数
|
|
if err := s.configureAndroid(decompileDir, config); err != nil {
|
|
return nil, fmt.Errorf("配置失败: %v", err)
|
|
}
|
|
|
|
// 4. 修改 AndroidManifest.xml
|
|
if err := s.modifyAndroidManifest(decompileDir); err != nil {
|
|
return nil, fmt.Errorf("修改 AndroidManifest 失败: %v", err)
|
|
}
|
|
|
|
// 5. 回编译 APK
|
|
unsignedAPK := filepath.Join(taskDir, "unsigned.apk")
|
|
if err := s.recompileAPK(decompileDir, unsignedAPK); err != nil {
|
|
return nil, fmt.Errorf("回编译失败: %v", err)
|
|
}
|
|
|
|
// 6. 签名 APK
|
|
signedAPK := filepath.Join(taskDir, "signed.apk")
|
|
if err := s.signAPK(unsignedAPK, signedAPK); err != nil {
|
|
return nil, fmt.Errorf("签名失败: %v", err)
|
|
}
|
|
|
|
// 7. 获取结果文件信息
|
|
fileInfo, err := os.Stat(signedAPK)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取文件信息失败: %v", err)
|
|
}
|
|
|
|
// 8. 复制结果文件到输出目录
|
|
outputDir := filepath.Join("uploads", "inject")
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("创建输出目录失败: %v", err)
|
|
}
|
|
|
|
outputFile := filepath.Join(outputDir, fmt.Sprintf("injected_%d.apk", time.Now().Unix()))
|
|
if err := copyFile(signedAPK, outputFile); err != nil {
|
|
return nil, fmt.Errorf("复制结果文件失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[INFO] Android APK 注入完成: %s", outputFile)
|
|
|
|
return &InjectResult{
|
|
Success: true,
|
|
ResultFile: "/" + strings.ReplaceAll(outputFile, "\\", "/"),
|
|
ResultSize: fileInfo.Size(),
|
|
}, nil
|
|
}
|
|
|
|
// injectWindows Windows PE 注入 (占位)
|
|
func (s *InjectService) injectWindows(inputFile string, config InjectConfig, taskDir string) (*InjectResult, error) {
|
|
return nil, fmt.Errorf("Windows 平台注入暂未实现")
|
|
}
|
|
|
|
// injectLinux Linux ELF 注入 (占位)
|
|
func (s *InjectService) injectLinux(inputFile string, config InjectConfig, taskDir string) (*InjectResult, error) {
|
|
return nil, fmt.Errorf("Linux 平台注入暂未实现")
|
|
}
|
|
|
|
// injectIOS iOS IPA 注入 (占位)
|
|
func (s *InjectService) injectIOS(inputFile string, config InjectConfig, taskDir string) (*InjectResult, error) {
|
|
return nil, fmt.Errorf("iOS 平台注入暂未实现")
|
|
}
|
|
|
|
// decompileAPK 反编译 APK
|
|
func (s *InjectService) decompileAPK(apkFile, outputDir string) error {
|
|
cmd := exec.Command("java", "-jar", s.ApktoolPath, "d", apkFile, "-o", outputDir, "-f")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("apktool d 失败: %v, output: %s", err, string(output))
|
|
}
|
|
log.Printf("[DEBUG] decompile output: %s", string(output))
|
|
return nil
|
|
}
|
|
|
|
// recompileAPK 回编译 APK
|
|
func (s *InjectService) recompileAPK(decompileDir, outputFile string) error {
|
|
cmd := exec.Command("java", "-jar", s.ApktoolPath, "b", decompileDir, "-o", outputFile)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("apktool b 失败: %v, output: %s", err, string(output))
|
|
}
|
|
log.Printf("[DEBUG] recompile output: %s", string(output))
|
|
return nil
|
|
}
|
|
|
|
// signAPK 签名 APK
|
|
func (s *InjectService) signAPK(unsignedAPK, signedAPK string) error {
|
|
// 如果没有配置 keystore,使用测试签名
|
|
if s.KeystorePath == "" {
|
|
log.Printf("[WARN] 未配置 keystore,使用 zipalign 对齐")
|
|
// 使用 zipalign 对齐
|
|
cmd := exec.Command("zipalign", "-v", "-p", "4", unsignedAPK, signedAPK)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("zipalign 失败: %v, output: %s", err, string(output))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 使用 apksigner 签名
|
|
cmd := exec.Command(s.ApksignerPath, "sign",
|
|
"--ks", s.KeystorePath,
|
|
"--ks-key-alias", s.KeystoreAlias,
|
|
"--ks-pass", "pass:"+s.KeystorePass,
|
|
"--key-pass", "pass:"+s.KeyPass,
|
|
"--out", signedAPK,
|
|
unsignedAPK)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("apksigner 签名失败: %v, output: %s", err, string(output))
|
|
}
|
|
log.Printf("[DEBUG] sign output: %s", string(output))
|
|
return nil
|
|
}
|
|
|
|
// mergeSmali 合并 smali 文件
|
|
func (s *InjectService) mergeSmali(templateDir, decompileDir string) error {
|
|
// 复制模板 smali 文件到反编译目录
|
|
smaliDirs := []string{"smali", "smali_classes2", "smali_classes3"}
|
|
for _, smaliDir := range smaliDirs {
|
|
srcSmali := filepath.Join(templateDir, smaliDir)
|
|
if _, err := os.Stat(srcSmali); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
dstSmali := filepath.Join(decompileDir, smaliDir)
|
|
if smaliDir == "smali" {
|
|
// 模板的 smali 直接覆盖
|
|
if err := copyDir(srcSmali, dstSmali); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// 其他 smali_classes 需要合并
|
|
if err := copyDir(srcSmali, dstSmali); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// configureAndroid 配置 Android 注入参数
|
|
func (s *InjectService) configureAndroid(decompileDir string, config InjectConfig) error {
|
|
// 查找并修改 HookApplication.java 中的配置
|
|
hookAppPath := filepath.Join(decompileDir, "smali", "com", "example", "shell", "HookApplication.smali")
|
|
if _, err := os.Stat(hookAppPath); os.IsNotExist(err) {
|
|
log.Printf("[WARN] HookApplication.smali 不存在,跳过配置")
|
|
return nil
|
|
}
|
|
|
|
// 读取文件内容
|
|
content, err := ioutil.ReadFile(hookAppPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// 替换配置参数
|
|
replacements := map[string]string{
|
|
"[OpenClassName]": config.AppName,
|
|
"\"https://example.com\"": fmt.Sprintf("\"%s\"", config.VerifyURL),
|
|
"[AppKey]": config.AppKey,
|
|
}
|
|
|
|
for old, new := range replacements {
|
|
contentStr = strings.ReplaceAll(contentStr, old, new)
|
|
}
|
|
|
|
// 写回文件
|
|
return ioutil.WriteFile(hookAppPath, []byte(contentStr), 0644)
|
|
}
|
|
|
|
// modifyAndroidManifest 修改 AndroidManifest.xml
|
|
func (s *InjectService) modifyAndroidManifest(decompileDir string) error {
|
|
manifestPath := filepath.Join(decompileDir, "AndroidManifest.xml")
|
|
content, err := ioutil.ReadFile(manifestPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// 检查是否已有 android:name
|
|
if strings.Contains(contentStr, "android:name=") {
|
|
// 替换现有的 android:name
|
|
re := strings.NewReplacer(
|
|
`android:name="android.app.Application"`, `android:name="com.example.shell.HookApplication"`,
|
|
)
|
|
contentStr = re.Replace(contentStr)
|
|
} else {
|
|
// 在 <application 标签添加 android:name
|
|
contentStr = strings.Replace(contentStr, "<application", `<application android:name="com.example.shell.HookApplication"`, 1)
|
|
}
|
|
|
|
return ioutil.WriteFile(manifestPath, []byte(contentStr), 0644)
|
|
}
|
|
|
|
// copyFile 复制文件
|
|
func copyFile(src, dst string) error {
|
|
input, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(dst, input, 0644)
|
|
}
|
|
|
|
// copyDir 复制目录
|
|
func copyDir(src, dst string) error {
|
|
// 确保目标目录存在
|
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
entries, err := ioutil.ReadDir(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
srcPath := filepath.Join(src, entry.Name())
|
|
dstPath := filepath.Join(dst, entry.Name())
|
|
|
|
if entry.IsDir() {
|
|
if err := copyDir(srcPath, dstPath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := copyFile(srcPath, dstPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSupportedPlatforms 获取支持的平台和架构
|
|
func GetSupportedPlatforms() map[string][]string {
|
|
return map[string][]string{
|
|
"android": {"x86", "x64", "arm", "arm64"},
|
|
"windows": {"x86", "x64"},
|
|
"linux": {"x86", "x64", "arm", "arm64"},
|
|
"ios": {"arm", "arm64"},
|
|
}
|
|
}
|
|
|
|
// DetectPlatform 检测文件平台
|
|
func DetectPlatform(filename string) (Platform, error) {
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
|
switch ext {
|
|
case ".apk":
|
|
return PlatformAndroid, nil
|
|
case ".exe", ".dll":
|
|
return PlatformWindows, nil
|
|
case ".so", "", ".elf":
|
|
if strings.Contains(strings.ToLower(filename), "linux") || ext == ".so" {
|
|
return PlatformLinux, nil
|
|
}
|
|
case ".ipa":
|
|
return PlatformIOS, nil
|
|
}
|
|
|
|
// 根据文件名猜测
|
|
lowerName := strings.ToLower(filename)
|
|
if strings.Contains(lowerName, "android") || strings.Contains(lowerName, "apk") {
|
|
return PlatformAndroid, nil
|
|
}
|
|
if strings.Contains(lowerName, "windows") || strings.Contains(lowerName, "win") || strings.Contains(lowerName, ".exe") {
|
|
return PlatformWindows, nil
|
|
}
|
|
if strings.Contains(lowerName, "linux") || strings.Contains(lowerName, ".so") {
|
|
return PlatformLinux, nil
|
|
}
|
|
if strings.Contains(lowerName, "ios") || strings.Contains(lowerName, ".ipa") {
|
|
return PlatformIOS, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("无法检测文件平台: %s", filename)
|
|
}
|
|
|
|
// ToJSON 转换为 JSON
|
|
func (c *InjectConfig) ToJSON() string {
|
|
data, _ := json.Marshal(c)
|
|
return string(data)
|
|
}
|
|
|
|
// ParseConfig 从 JSON 解析配置
|
|
func ParseConfig(jsonStr string) (*InjectConfig, error) {
|
|
var config InjectConfig
|
|
err := json.Unmarshal([]byte(jsonStr), &config)
|
|
return &config, err
|
|
}
|