跳到主要内容

ios马甲软件实现原理

iOS 里的“马甲软件”通常有两种含义:

  1. 合法的白标 App:一套代码,为不同客户或品牌生成多个 App。
  2. 灰产马甲包:通过更换名称、图标、Bundle ID、页面或远程配置,重复上架或规避审核。

从技术实现看,两者底层非常接近,区别主要在于:业务是否真实独立、分发方式是否合规,以及是否存在规避审核的行为。

一、本质:一套代码生成多个独立 IPA

iOS 并不是像部分 Android 系统那样直接“双开”已经安装的 App。通常做法是由同一套代码生成多个独立应用:

共享业务代码
├── BrandA Target
│ ├── com.company.brand-a
│ ├── A 图标
│ ├── A 名称
│ └── A 接口配置

├── BrandB Target
│ ├── com.company.brand-b
│ ├── B 图标
│ ├── B 名称
│ └── B 接口配置

└── BrandC Target
├── com.company.brand-c
├── C 图标
├── C 名称
└── C 接口配置

每个 App 都拥有独立的:

  • Bundle ID
  • 签名和 Provisioning Profile
  • App Store Connect 应用记录
  • 图标和展示名称
  • 沙盒数据目录
  • 推送证书或 APNs 配置
  • Universal Links、Associated Domains 等能力

所以用户看到的是多个 App,但它们可能有 90% 以上的代码来自同一个仓库。

二、最常见的实现:Xcode 多 Target

Xcode 原生支持在一个 Project 中配置多个 Target。每个 Target 可以设置自己的源文件、资源、签名和构建参数,并生成不同的产品。

典型工程结构:

App/
├── Shared/
│ ├── Networking/
│ ├── User/
│ ├── Order/
│ └── Payment/
├── Brands/
│ ├── BrandA/
│ │ ├── Assets.xcassets
│ │ └── BrandA.xcconfig
│ └── BrandB/
│ ├── Assets.xcassets
│ └── BrandB.xcconfig
└── App.swift

每个品牌使用不同的 .xcconfig

// BrandA.xcconfig
PRODUCT_BUNDLE_IDENTIFIER = com.company.brand-a
APP_DISPLAY_NAME = 品牌 A
API_BASE_URL = https://api.brand-a.example
TENANT_ID = brand-a
// BrandB.xcconfig
PRODUCT_BUNDLE_IDENTIFIER = com.company.brand-b
APP_DISPLAY_NAME = 品牌 B
API_BASE_URL = https://api.brand-b.example
TENANT_ID = brand-b

然后在 Info.plist 中引用构建变量:

<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>

<key>APIBaseURL</key>
<string>$(API_BASE_URL)</string>

<key>TenantID</key>
<string>$(TENANT_ID)</string>

代码里统一读取配置:

enum AppConfiguration {
static let apiBaseURL: URL = {
guard
let value = Bundle.main.object(
forInfoDictionaryKey: "APIBaseURL"
) as? String,
let url = URL(string: value)
else {
fatalError("APIBaseURL 配置无效")
}

return url
}()

static let tenantID: String = {
guard
let value = Bundle.main.object(
forInfoDictionaryKey: "TenantID"
) as? String
else {
fatalError("TenantID 配置无效")
}

return value
}()
}

业务代码不需要关心当前运行的是哪个品牌:

let endpoint = AppConfiguration.apiBaseURL
let tenantID = AppConfiguration.tenantID

三、运行时通过租户配置切换

另一种实现方式不是为每个品牌编译大量不同代码,而是只在安装包中保存一个 tenantId

com.company.brand-a → tenantId = brand-a
com.company.brand-b → tenantId = brand-b

App 启动时请求配置中心:

GET /app-config?tenantId=brand-a

服务端返回对应租户的界面和功能配置:

{
"brandName": "品牌 A",
"primaryColor": "#1677FF",
"homeModules": [
"banner",
"category",
"recommend"
],
"features": {
"coupon": true,
"membership": false
}
}

客户端根据配置构建界面:

本地固定能力
├── 登录
├── 网络层
├── 支付
├── 推送
├── WebView
└── 页面容器

远程动态配置
├── 品牌名称
├── 主题色
├── 首页模块
├── 功能开关
├── 运营内容
└── 接口租户

这种模式本质上是:

白标 App
+ 多租户后端
+ Feature Flag
+ Server-Driven UI

它的优点是:

  • 修复一个 Bug,所有品牌复用;
  • 新增功能可以按租户灰度;
  • 运营内容不需要每次重新发版;
  • CI 可以自动批量生成多个 IPA;
  • 品牌差异集中在配置层,而不是复制业务代码。

但是,不能使用远程配置在审核通过后偷偷切换成审核时未展示的违规业务。Apple 明确禁止欺骗审核和操纵 App Store 的行为,严重时可能导致 App 下架或开发者账号被移除。

四、WebView 套壳型马甲

部分马甲 App 实际上只有一个原生外壳:

原生 App
├── 启动页
├── 登录
├── 推送
├── 相机和相册
├── 支付能力
└── WKWebView

加载 H5

核心业务全部由 H5 提供:

import UIKit
import WebKit

final class WebContainerViewController: UIViewController {
private let webView = WKWebView(
frame: .zero,
configuration: WKWebViewConfiguration()
)

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(webView)
webView.frame = view.bounds

let url = AppConfiguration.apiBaseURL
webView.load(URLRequest(url: url))
}
}

不同马甲只需要修改:

  • Bundle ID
  • 图标和名称
  • H5 首页地址
  • 主题色
  • 接口域名
  • 租户标识

这也是为什么部分 App 看起来完全不同,但安装包大小、交互习惯和 Bug 都非常相似。

不过,单纯将网站重新封装成 App 很容易触发 App Store 审核规则。Apple 的 Minimum Functionality 规则要求 App 在功能、内容和 UI 上超越“重新包装的网站”,并限制主要由网页剪辑、链接集合或营销内容组成的 App。

五、跨端框架批量生成

大量白标或马甲 App 会使用跨端技术:

  • uni-app
  • React Native
  • Flutter
  • Capacitor
  • Cordova
  • 自研 WebView 容器

例如 uni-app:

共享 Vue 业务代码

不同品牌环境变量

生成不同 manifest.json

云打包或 Xcode Archive

多个 IPA

配置可以统一建模:

interface BrandConfig {
appName: string;
tenantId: string;
apiBaseURL: string;
primaryColor: string;
}

const BRAND_CONFIGS = {
brandA: {
appName: '品牌 A',
tenantId: 'brand-a',
apiBaseURL: 'https://api.brand-a.example',
primaryColor: '#1677FF',
},
brandB: {
appName: '品牌 B',
tenantId: 'brand-b',
apiBaseURL: 'https://api.brand-b.example',
primaryColor: '#07C160',
},
} satisfies Record<string, BrandConfig>;

CI 按品牌矩阵构建:

brand-a → 注入配置 → build → 签名 → brand-a.ipa
brand-b → 注入配置 → build → 签名 → brand-b.ipa
brand-c → 注入配置 → build → 签名 → brand-c.ipa

正规的工程通常使用以下方式管理差异:

  • .xcconfig
  • Xcode Scheme
  • Build Configuration
  • 环境变量
  • 配置生成脚本
  • 独立 Asset Catalog
  • CI Matrix

不建议在构建过程中直接修改共享源代码,因为这样难以追踪、并发构建容易相互污染,也不利于审计。

六、为什么部分马甲没有从 App Store 安装

还有一部分所谓“马甲 App”,实际上使用了其他分发方式。

6.1 企业内部分发

通过 Apple Developer Enterprise Program 签名并分发 .ipa

企业内部网站或 MDM

安装企业签名 App

设备验证企业证书

Enterprise Program 只适用于组织内部员工使用,不能用于向普通公众分发。

如果企业证书被撤销,相关 App 可能无法继续安装或启动。因此部分非正规 App 会出现以下现象:

今天可以安装
明天提示“无法验证 App”
过几天又更换下载地址

这种情况通常与签名证书失效或被撤销有关。

6.2 Custom Apps

如果 App 是给特定企业、经销商或加盟商使用,可以通过 Apple Business Manager 私有分发 Custom App。

它仍然需要:

  • 提交到 App Store Connect;
  • 接受 Apple 审核;
  • 指定可获取该 App 的组织;
  • 通过 Apple Business Manager 或 MDM 分发。

这是 B2B 白标 App 更正规的分发方案。

6.3 MDM

企业管理设备可以通过 MDM 下发 Managed App,并为不同组织或设备注入配置。

这种模式甚至不需要为每个客户生成独立 App:

同一个 App
+
MDM 下发不同 tenantId、API 地址和品牌配置

适合:

  • 企业内部工具;
  • 连锁门店设备;
  • 专用业务终端;
  • 受管 iPhone 和 iPad;
  • 对外不可见的 B2B 应用。

七、为什么 App Store 里仍然能看到很多马甲

技术上可以批量生成,并不代表 App Store 审核规则允许批量上架。

Apple 当前审核规则限制:

  • 为同一个 App 创建多个 Bundle ID;
  • 只修改名称、图标或界面后重复提交;
  • 提交与现有 App 没有明显差异的变体;
  • 批量提交低质量模板 App;
  • 使用模板生成服务代替内容提供者批量上架;
  • 在审核后切换成未向 Apple 展示的业务。

但仍然可能看到类似 App,通常存在以下情况:

  1. App 分别属于不同品牌和内容所有者;
  2. 每个 App 都有真实独立的业务内容;
  3. 分别由对应的内容提供商提交;
  4. App 之间的差异足以被认为是独立产品;
  5. 使用 Custom Apps、MDM 等非公开分发;
  6. 暂时通过审核,但后续仍可能被清理;
  7. 存在远程切换内容等违规行为,但尚未被发现。

Apple 的 Spam 规则明确建议,将地区、球队、学校等同类变体合并到一个 App 中,而不是为相同 App 创建多个 Bundle ID。

八、合法白标 App 的推荐架构

如果业务确实需要支持多个品牌,推荐使用以下结构:

App Shell
├── UI Design System
├── Domain Modules
├── Network Layer
├── Authentication
├── Payment
├── Push Notification
└── Analytics

Brand Configuration
├── Bundle ID
├── Display Name
├── Assets
├── Theme Tokens
├── Tenant ID
├── API Endpoint
└── Feature Flags

Backend
├── Tenant Isolation
├── Remote Configuration
├── Content Management
└── Feature Management

优先级建议:

一个 App + 登录后切换租户

一个 App + Server-Driven UI

Custom Apps / Apple Business Manager

确有独立产品价值时再生成多个 App

如果最终需要多个独立 App,应确保每个 App 至少在以下方面具有真实差异:

  • 不同内容所有者;
  • 不同用户体系或服务范围;
  • 不同业务流程;
  • 不同核心功能;
  • 不同法律主体或区域服务;
  • 不只是图标、名称、颜色和域名不同。

九、风险边界

以下行为技术上可能实现,但属于高风险甚至违规行为:

  • 隐藏审核期间不可见的功能;
  • 审核通过后远程切换成另一套违规业务;
  • 伪造 App 之间的功能差异;
  • 滥用企业证书向公众分发;
  • 使用动态代码下载绕过审核;
  • 通过多个账号重复提交相同 App;
  • 使用误导性元数据、截图或审核账号。

这些做法可能导致:

  • App 被下架;
  • 企业证书被撤销;
  • 开发者账号被封禁;
  • 关联账号受到审查;
  • 已安装 App 无法继续运行;
  • 用户数据和支付安全风险。

本文只讨论合法白标、多租户、跨端构建和企业分发的工程实现,不提供规避审核或滥用签名的具体操作方案。

总结

所谓 iOS 马甲 App,技术本质通常是:

共享代码库
+ 多 Target / Scheme
+ 多 Bundle ID
+ 多套图标和名称
+ 多租户后端
+ 远程配置
+ 自动化签名和打包

其中:

  • 多 Target 负责生成不同安装包;
  • .xcconfig 负责隔离品牌构建参数;
  • 多租户后端 负责隔离数据和业务;
  • 远程配置 负责控制功能和运营内容;
  • CI Matrix 负责批量构建、签名和发布;
  • Custom Apps / MDM 负责合规的企业分发。

如果只是更换图标、名称和 Bundle ID,而核心内容完全相同地重复上架,技术上容易实现,但属于 Apple App Store 重点限制的 Spam/马甲包场景。

参考资料

核对日期:2026-07-21。