Swift 容易受到代码注入的攻击吗?
Is Swift vulnerable to code injection?
我正在阅读有关 Cycript and Cydia Substrate 的内容以及它们如何用于对 iOS 应用程序进行代码注入攻击。如果您在高度安全的环境中工作,这样的代码应该会吓到您。 (忽略/etc/password部分,只考虑将originalMessage替换为crackedMessage即可。)
cy# MS.hookFunction(fopen, function(path, mode) {
cy> if (path == "/etc/passwd")
cy> path = "/var/passwd-fake";
cy> var file = (*oldf)(path, mode);
cy> log.push([path, mode, file]);
cy> return file;
cy> }, oldf)
我读了一篇博客(我没有保存),它说 Swift 不像 Objective-C 那样容易受到攻击,因为它不是动态的。再一次,我也读到你可以做 method swizzling in Swift 所以我不清楚 Swift 是否提供任何针对代码注入攻击的保护。
那么,Swift 是否容易受到代码注入攻击?
最终,如果您允许 运行 在他们的设备上使用您的程序,则无法阻止他们劫持您的程序。有办法让它变得更难,但没有办法让它变得不可能。
我可以想到将代码注入应用程序的这些主要方法:
- 调配 Objective-C 方法与 运行 时间;
- 通过解析 executable 并确定要更改的正确位来调整虚拟 Swift 方法;
- 修改调用目标;
- 通过更改符号存根目标调配导入的符号;
- 使用 dyld 强制加载库或更改程序加载的库;
- 替换您的程序链接的库。
在用户完全控制的环境中,没有 100% 有效的方法来防止这些情况发生。您应该根据您的威胁模型来决定是否担心。
Swizzling Objective-C 方法与 运行time
方法调配是一种技术,您可以在 运行 时间使用任意不同的代码(通常用于不同的目的)更改方法的实现。常见用例是绕过检查或记录参数。
Objective-C 中的 Swizzling 是一件大事,因为 运行 时代需要标识每个方法和每个实例字段的元数据。我不知道有任何其他语言可以编译为本机机器代码并保留这么多元数据。如果你有像 -[AccessControl validatePassword:]
这样的东西,你只是让坏人很容易上手。使用 method_setImplementation
,这只是在乞求发生。
由于Swift classes 可以继承自Objective-C classes,这仍然需要寻找。但是,从 Objective-C class 继承的 classes 上的新方法仅在 Objective-C 运行 时间公开,如果它们具有 @objc
属性(或者如果 class 本身具有 @objc
属性),因此与 Objective-C.
相比,这限制了攻击面
此外,Swift 编译器可能会绕过 Objective-C 运行 时间来调用、去虚拟化或内联 Swift 未标记 dynamic
的方法,即使它们被标记为 @objc
。这意味着在某些情况下,调配可能仅适用于通过 Objective-C.
调度的调用
当然,如果您的 class 或方法没有暴露给 Objective-C 运行 时间,那是完全不可能的。
通过解析 executable 并确定要更改的正确位来调整虚拟 Swift 方法
但是,您不需要 Objective-C 运行时间来交换方法实现。 Swift 的虚拟方法仍然有虚拟 tables,截至 2015 年 2 月,它们位于 executable 的 __DATA
段中。它是 writable,所以如果你能找出要更改的正确位,应该可以调配 Swift 虚拟方法。对此没有方便的API。
C++ classes 可以类似地修改,但是 Swift 方法默认是虚拟的,攻击面要大得多。如果编译器没有发现覆盖,则允许编译器将方法去虚拟化作为优化,但依赖编译器优化作为安全功能是不负责任的。
默认情况下,部署的 Swift executable 是 stripped。非 public
/open
符号的信息被丢弃,与 Objective-C 相比,这使得识别您想要更改的符号变得更加困难。 Public
/open
符号未被删除,因为假定其他外部代码客户端可能需要它们。
然而,如果有人弄清楚他们想要换出哪个函数实现,他们所要做的就是将新实现的地址写入正确的虚拟 table 槽中。他们可能需要制作自己的 Mach-O 解析器,但这肯定不会超出制作 Cycript 之类东西的人的范围。
最后,final
方法降低了这种风险,因为编译器不需要通过 vtable 调用它们。此外,struct
方法永远不是虚拟的。
修改调用目标
如果一切都失败了,您的攻击者仍然可以遍历您的机器代码并将 bl
或 call
指令操作数更改为他们更喜欢的任何位置。这更加复杂,并且 hard/impossible 可以通过自动化方法获得 100% 的正确率,尤其是在缺少符号的情况下,但是足够坚定的人将能够做到这一点。您决定是否有人最终会觉得为您的应用程序做这件事是值得的。
这适用于虚拟和非虚拟方法。但是,当编译器内联调用时,这非常困难。
通过更改符号存根目标调配导入的符号
任何导入的符号,无论其编写语言和使用语言如何,都容易受到 swizzling 的影响。这是因为外部符号在 运行 时被绑定。每当您使用外部库中的函数时,编译器都会在查找 table 中生成一个条目。这是一个示例,说明如果将 executable 返回给 C 代码,对 fopen
的调用可能会是什么样子:
FILE* locate_fopen(const char* a, const char* b) {
fopen_stub = dyld->locate("fopen"); // find actual fopen and replace stub pointer to it
return fopen_stub(a, b);
}
FILE* (*fopen_stub)(const char*, const char*) = &locate_fopen;
int main() {
FILE* x = fopen_stub("hello.txt", "r");
}
对 fopen_stub
的初始调用找到实际的 fopen
,并用它替换 fopen_stub
指向的地址。这样,dyld 根本不需要在启动 运行ning 之前解析程序及其库中使用的数千个外部符号。但是,这意味着攻击者可以将 fopen_stub
替换为他们想要调用的任何函数的地址。这就是您的 Cycript 示例所做的。
如果不编写自己的链接器和动态链接器,您对这种攻击的唯一保护就是不使用共享库或框架。这在现代开发环境中不是一个可行的解决方案,因此您可能不得不处理它。
可能有一些方法可以确保存根到达您期望的位置,但这有点不稳定,而且这些检查总是可以被坚定的攻击者nop
排除掉。此外,您无法在无法控制调用导入符号的共享库之前插入这些检查。如果攻击者决定用他们控制的共享库替换共享库,这些检查也将毫无用处。
顺便说一句,启动闭包允许 dyld 3 将这些查找 table 替换为预绑定信息。我不认为 launch closures 目前是只读的,但看起来它们最终可能是只读的。如果是,那么混合符号将变得更难。
使用 dyld 强制加载库或更改程序加载的库
Dyld supports 将库强制加载到您的 executable。此功能可用于替换您的 executable 使用的任何导入符号。不喜欢正常的 fopen
?写一个重新定义它的dylib
!
如果 executable 被标记为受限,Dyld 将不会与此方法合作。有three ways达到这个状态(寻找pruneEnvironmentVariables
):
- 在您的 execu 上启用 setuid 位或 setgid 位table;
- 进行代码签名并拥有 "Restricted" OS X-only 权利;
- 在名为
__RESTRICT
的段中有一个名为 __restrict
的部分。
您可以使用以下 "Other Linker Flags" 创建 __restrict
部分和 __RESTRICT
部分:
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
请注意,所有这些都很容易损坏。当用户控制执行环境时,setuid 和 setgid 位很容易清除,代码签名很容易删除,并且只需重命名部分或段即可摆脱限制状态。
替换您的程序链接的库
如果所有其他方法都失败了,攻击者仍然可以替换您的 executable 用来使它为所欲为的共享库。你无法控制它。
tl;博士
在 Swift 应用程序中注入代码比 Objective-C 应用程序更难,但仍有可能。大多数可用于注入代码的方法都是与语言无关的,这意味着没有语言会让你更安全。
在大多数情况下,您无法采取任何措施来保护自己免受这种情况的影响。只要用户控制执行环境,您的代码就运行宁作为他们系统上的访客,他们几乎可以用它做任何他们想做的事。
你说的是越狱 iOS 设备上的代码注入。很简单:用户已经移除了他们的 OS 保护,所以现在一切正常。没有安全感。如果用户没有自愿移除该保护,那么进入应用程序的地址 space 是不可能的。
我正在阅读有关 Cycript and Cydia Substrate 的内容以及它们如何用于对 iOS 应用程序进行代码注入攻击。如果您在高度安全的环境中工作,这样的代码应该会吓到您。 (忽略/etc/password部分,只考虑将originalMessage替换为crackedMessage即可。)
cy# MS.hookFunction(fopen, function(path, mode) {
cy> if (path == "/etc/passwd")
cy> path = "/var/passwd-fake";
cy> var file = (*oldf)(path, mode);
cy> log.push([path, mode, file]);
cy> return file;
cy> }, oldf)
我读了一篇博客(我没有保存),它说 Swift 不像 Objective-C 那样容易受到攻击,因为它不是动态的。再一次,我也读到你可以做 method swizzling in Swift 所以我不清楚 Swift 是否提供任何针对代码注入攻击的保护。
那么,Swift 是否容易受到代码注入攻击?
最终,如果您允许 运行 在他们的设备上使用您的程序,则无法阻止他们劫持您的程序。有办法让它变得更难,但没有办法让它变得不可能。
我可以想到将代码注入应用程序的这些主要方法:
- 调配 Objective-C 方法与 运行 时间;
- 通过解析 executable 并确定要更改的正确位来调整虚拟 Swift 方法;
- 修改调用目标;
- 通过更改符号存根目标调配导入的符号;
- 使用 dyld 强制加载库或更改程序加载的库;
- 替换您的程序链接的库。
在用户完全控制的环境中,没有 100% 有效的方法来防止这些情况发生。您应该根据您的威胁模型来决定是否担心。
Swizzling Objective-C 方法与 运行time
方法调配是一种技术,您可以在 运行 时间使用任意不同的代码(通常用于不同的目的)更改方法的实现。常见用例是绕过检查或记录参数。
Objective-C 中的 Swizzling 是一件大事,因为 运行 时代需要标识每个方法和每个实例字段的元数据。我不知道有任何其他语言可以编译为本机机器代码并保留这么多元数据。如果你有像 -[AccessControl validatePassword:]
这样的东西,你只是让坏人很容易上手。使用 method_setImplementation
,这只是在乞求发生。
由于Swift classes 可以继承自Objective-C classes,这仍然需要寻找。但是,从 Objective-C class 继承的 classes 上的新方法仅在 Objective-C 运行 时间公开,如果它们具有 @objc
属性(或者如果 class 本身具有 @objc
属性),因此与 Objective-C.
此外,Swift 编译器可能会绕过 Objective-C 运行 时间来调用、去虚拟化或内联 Swift 未标记 dynamic
的方法,即使它们被标记为 @objc
。这意味着在某些情况下,调配可能仅适用于通过 Objective-C.
当然,如果您的 class 或方法没有暴露给 Objective-C 运行 时间,那是完全不可能的。
通过解析 executable 并确定要更改的正确位来调整虚拟 Swift 方法
但是,您不需要 Objective-C 运行时间来交换方法实现。 Swift 的虚拟方法仍然有虚拟 tables,截至 2015 年 2 月,它们位于 executable 的 __DATA
段中。它是 writable,所以如果你能找出要更改的正确位,应该可以调配 Swift 虚拟方法。对此没有方便的API。
C++ classes 可以类似地修改,但是 Swift 方法默认是虚拟的,攻击面要大得多。如果编译器没有发现覆盖,则允许编译器将方法去虚拟化作为优化,但依赖编译器优化作为安全功能是不负责任的。
默认情况下,部署的 Swift executable 是 stripped。非 public
/open
符号的信息被丢弃,与 Objective-C 相比,这使得识别您想要更改的符号变得更加困难。 Public
/open
符号未被删除,因为假定其他外部代码客户端可能需要它们。
然而,如果有人弄清楚他们想要换出哪个函数实现,他们所要做的就是将新实现的地址写入正确的虚拟 table 槽中。他们可能需要制作自己的 Mach-O 解析器,但这肯定不会超出制作 Cycript 之类东西的人的范围。
最后,final
方法降低了这种风险,因为编译器不需要通过 vtable 调用它们。此外,struct
方法永远不是虚拟的。
修改调用目标
如果一切都失败了,您的攻击者仍然可以遍历您的机器代码并将 bl
或 call
指令操作数更改为他们更喜欢的任何位置。这更加复杂,并且 hard/impossible 可以通过自动化方法获得 100% 的正确率,尤其是在缺少符号的情况下,但是足够坚定的人将能够做到这一点。您决定是否有人最终会觉得为您的应用程序做这件事是值得的。
这适用于虚拟和非虚拟方法。但是,当编译器内联调用时,这非常困难。
通过更改符号存根目标调配导入的符号
任何导入的符号,无论其编写语言和使用语言如何,都容易受到 swizzling 的影响。这是因为外部符号在 运行 时被绑定。每当您使用外部库中的函数时,编译器都会在查找 table 中生成一个条目。这是一个示例,说明如果将 executable 返回给 C 代码,对 fopen
的调用可能会是什么样子:
FILE* locate_fopen(const char* a, const char* b) {
fopen_stub = dyld->locate("fopen"); // find actual fopen and replace stub pointer to it
return fopen_stub(a, b);
}
FILE* (*fopen_stub)(const char*, const char*) = &locate_fopen;
int main() {
FILE* x = fopen_stub("hello.txt", "r");
}
对 fopen_stub
的初始调用找到实际的 fopen
,并用它替换 fopen_stub
指向的地址。这样,dyld 根本不需要在启动 运行ning 之前解析程序及其库中使用的数千个外部符号。但是,这意味着攻击者可以将 fopen_stub
替换为他们想要调用的任何函数的地址。这就是您的 Cycript 示例所做的。
如果不编写自己的链接器和动态链接器,您对这种攻击的唯一保护就是不使用共享库或框架。这在现代开发环境中不是一个可行的解决方案,因此您可能不得不处理它。
可能有一些方法可以确保存根到达您期望的位置,但这有点不稳定,而且这些检查总是可以被坚定的攻击者nop
排除掉。此外,您无法在无法控制调用导入符号的共享库之前插入这些检查。如果攻击者决定用他们控制的共享库替换共享库,这些检查也将毫无用处。
顺便说一句,启动闭包允许 dyld 3 将这些查找 table 替换为预绑定信息。我不认为 launch closures 目前是只读的,但看起来它们最终可能是只读的。如果是,那么混合符号将变得更难。
使用 dyld 强制加载库或更改程序加载的库
Dyld supports 将库强制加载到您的 executable。此功能可用于替换您的 executable 使用的任何导入符号。不喜欢正常的 fopen
?写一个重新定义它的dylib
!
如果 executable 被标记为受限,Dyld 将不会与此方法合作。有three ways达到这个状态(寻找pruneEnvironmentVariables
):
- 在您的 execu 上启用 setuid 位或 setgid 位table;
- 进行代码签名并拥有 "Restricted" OS X-only 权利;
- 在名为
__RESTRICT
的段中有一个名为__restrict
的部分。
您可以使用以下 "Other Linker Flags" 创建 __restrict
部分和 __RESTRICT
部分:
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
请注意,所有这些都很容易损坏。当用户控制执行环境时,setuid 和 setgid 位很容易清除,代码签名很容易删除,并且只需重命名部分或段即可摆脱限制状态。
替换您的程序链接的库
如果所有其他方法都失败了,攻击者仍然可以替换您的 executable 用来使它为所欲为的共享库。你无法控制它。
tl;博士
在 Swift 应用程序中注入代码比 Objective-C 应用程序更难,但仍有可能。大多数可用于注入代码的方法都是与语言无关的,这意味着没有语言会让你更安全。
在大多数情况下,您无法采取任何措施来保护自己免受这种情况的影响。只要用户控制执行环境,您的代码就运行宁作为他们系统上的访客,他们几乎可以用它做任何他们想做的事。
你说的是越狱 iOS 设备上的代码注入。很简单:用户已经移除了他们的 OS 保护,所以现在一切正常。没有安全感。如果用户没有自愿移除该保护,那么进入应用程序的地址 space 是不可能的。