Swift容易被代码注入?

我正在阅读有关Cycript和Cydia Substrate的内容,以及如何将它们用于iOS应用程序的代码注入攻击。 这样的代码应该吓到你,如果你在一个高度安全的环境下工作。 (忽略/ etc / password部分,只要考虑使用crackedMessagereplaceoriginalMessage 即可 。)

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那样脆弱,因为它不像dynamic一样。 再次,我也读过,你可以在Swift中做方法调整,所以我不清楚Swift是否提供了对代码注入攻击的保护。

那么Swift很容易受到代码注入攻击?

最终,如果您让程序在设备上运行,则无法阻止某人劫持您的程序。 有办法让它更难,但是没有办法使它变得不可能。

在最底层,调整方法只是修改你的可执行文件。 代码签名确保不会发生,但是如果攻击者不关心签名,那么他显然可以自由修改它。

我可以考虑将代码注入应用程序的这些主要方法:

  • 使用运行时debugging的Objective-C方法;
  • 通过parsing出可执行文件并找出正确的位来改变虚拟的Swift方法。
  • 修改通话目标;
  • 通过改变符号存根目标来改变导入的符号;
  • 使用dyld强制加载库或更改程序加载的库;
  • replace您的程序链接的库。

而且,在用户完全控制的环境中,没有100%有效的方法来防止这些方法中的任何一种。 您应该根据您的威胁模型来决定是否担心。

与运行时一起运行的Objective-C方法

方法swizzling是一种技术,您可以在运行时使用任意不同的代码(通常用于不同的目的)更改方法的实现。 常见用例绕过检查或logging参数。

Objective-C中的Swizzling是一个巨大的事情,因为运行时需要元数据来标识每个方法和每个实例字段。 我不知道任何其他语言编译为本地机器代码,并保持这个周围的元数据。 如果你有类似于-[AccessControl validatePassword:] ,那么对于坏人来说真的很简单。 用method_setImplementation ,这只是乞求发生。

由于Swift类可以inheritanceObjective-C类,所以这仍然是需要查找的东西。 但是,从Objective-C类inheritance的类的新方法只有在具有@objc属性(或者类本身具有@objc属性)时才会暴露给Objective-C运行库,所以这会限制比较的攻击面到Objective-C。

此外,Swift编译器可以绕过Objective-C运行时调用,虚拟化或embeddedSwift方法,即使它们被标记为@objc ,也不标记为dynamic 。 这意味着在某些情况下,只有通过Objective-C调用的调用才可能发生混合。

当然,如果你的类或方法没有暴露给Objective-C运行时,这是完全不可能的。

通过parsing出可执行文件并找出正确的位来改变虚拟Swift方法

但是,您不需要Objective-C运行库来交换方法实现。 Swift的虚拟方法仍然有虚拟表,截至2015年2月,它们位于可执行文件的__DATA段。 它是可写的,所以如果你能找出正确的位来改变Swift虚拟方法应该是可能的。 这个没有方便的API。

C ++类可以类似地修改,但Swift方法默认是虚拟的,攻击面更大。 如果编译器找不到覆盖,则允许编译器将方法虚拟化为优化,但依靠编译器优化作为安全function不负责任。

默认情况下,部署的Swift可执行文件是strip 。 放弃非public / open符号的信息,这使得与Objective-C相比,您很难识别想要改变的符号。 Public / open符号不会被剥离,因为假定其他外部代码客户端可能需要它们。

但是,如果有人想知道哪个function实现需要交换,他们所要做的就是将新实现的地址写入正确的虚拟表格槽中。 他们可能需要制作他们自己的Mach-Oparsing器,但这当然不会超出像Cycript那样的人的范围。

最后, final方法减less了这种风险,因为编译器不需要通过vtable调用它们。 另外, struct方法永远不是虚拟的。

修改调用目标

如果一切都失败了,你的攻击者仍然可以遍历你的机器代码,并将blcall指令的操作数改为他们想要的更好的地方。 这是更多的参与和相当困难/不可能得到100%正确的自动化方法,特别是如果符号不见了,但有足够的决心能够做到这一点。 你决定是否有人最终会觉得值得麻烦去做你的申请。

这适用于虚拟和非虚拟方法。 然而,当编译器内联调用时,这是非常困难的。

通过更改符号存根目标来改变导入的符号

任何导入的符号,无论它被写入的语言和其使用的语言如何,都容易混乱。 这是因为外部符号在运行时被延迟绑定。 无论何时从外部库使用函数,编译器都会在查找表中生成一个条目。 如果您将可执行文件返回给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的初始调用find实际的fopen ,并用fopen_stubreplace它所指向的地址。 这样,在开始运行之前,dyld不需要parsing程序及其库中使用的数千个外部符号。 但是,这意味着攻击者可以用fopen_stubreplace他们想调用的任何函数的地址。 这就是你的Cycript的例子。

在编写自己的链接器和dynamic链接器之后,您唯一的防范此类攻击的方法是不使用共享库或框架。 在现代开发环境中,这不是一个可行的解决scheme,所以你可能不得不面对这个问题。

可以有办法确保存根到达你所期望的位置,但是这样会有一些片断,而这些检查总是被一个坚定的攻击者取消。 此外,您将无法在共享库之前插入这些检查,因而无法控制调用导入的符号。 如果攻击者决定用他们所控制的共享库来replace共享库,那么这些检查也是无用的。

使用dyld强制加载库或更改程序加载的库

Dyld 支持强制加载库到你的可执行文件中。 这个function可以用来replace你的可执行程序使用的任何导入的符号。 不喜欢正常的fopen ? 写一个重新定义它的dylib

如果可执行文件被标记为受限,Dyld将不会与此方法配合使用。 有三种方法可以实现这种状态(查找pruneEnvironmentVariables ):

  • 启用可执行文件中的setuid位或setgid位;
  • 进行代码签名并拥有“受限”的仅限于OS X的权利;
  • 在称为__RESTRICT的段中有一个名为__restrict的段。

您可以使用以下“其他链接器标志”创build__restrict节和__RESTRICT节:

 -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null 

请注意,所有这些都很容易打破。 当用户控制执行环境时,setuid和setgid位是微不足道的,一个代码签名很容易被删除,而这个段或者段只需要重命名就可以摆脱被限制的状态。

replace程序链接的库

如果一切都失败了,攻击者仍然可以replace你的可执行文件所使用的共享库,使其做任何他们喜欢的事情。 你无法控制。

TL;博士

在Swift应用程序中注入代码比Objective-C应用程序更难,但仍然有可能。 大部分可用于注入代码的方法都是独立于语言的,这意味着没有语言会使您更安全。

大多数情况下,你可以做任何事情来保护自己免受这种伤害。 只要用户控制执行环境,你的代码在系统上作为一个客户运行,他们可以做任何他们想做的事情。

你正在谈论越狱的iOS设备上的代码注入。 非常简单:用户已经删除了他们的操作系统保护,所以现在什么都行。 没有安全。 如果用户没有自愿移除该保护,则不可能进入应用程序的地址空间。