Paranoid Penguin - shc,Shell 加密实用程序的局限性
shc 是一款流行的工具,用于保护包含敏感信息的 shell 脚本,例如密码。它的流行部分是由于审计员对脚本中密码的担忧。 shc 使用 RC4 加密 shell 脚本,从 shell 脚本中生成可执行二进制文件,并将其作为普通的 shell 脚本运行。虽然生成的二进制文件包含加密密码和加密的 shell 脚本,但它对随意查看是隐藏的。
起初,我对 shc 实用程序 (www.datsi.fi.upm.es/~frosal/sources/shc.html) 很感兴趣,并认为它是维护敏感 shell 脚本安全性的有价值的工具。然而,经过进一步检查,我能够从 shc 生成的 3.7 版本可执行文件中提取原始 shell 脚本。由于加密密钥存储在二进制可执行文件中,因此任何具有可执行文件读取权限的人都可以恢复原始 shell 脚本。本文详细介绍了从 shc 生成的二进制文件中提取原始 shell 可执行文件的过程。
shc 是一个通用的 shell 脚本编译器。从根本上说,shc 将 shell 脚本作为输入,将其转换为 C 程序,并运行编译器来编译 C 代码。C 程序包含使用 RC4 加密通过任意密钥加密的原始脚本。RC4 是 Ron Rivest 于 1987 年在 RSA 实验室设计的一种流密码。这种密码广泛应用于商业应用,包括 Oracle SQL 和 SSL。清单 1 演示了运行 shc。
清单 1. 运行 shc
[user1@shiraz test]# cat pub.sh #!/bin/sh echo "Hello World" user1@shiraz test]# ./pub.sh Hello World [user1@shiraz test]# shc -v -r -f pub.sh shc shll=sh shc [-i]=-c shc [-x]=exec '%s' "$@" shc [-l]= shc opts= shc: cc pub.sh.x.c -o pub.sh.x shc: strip pub.sh.x [user1@shiraz test]# ls pub.sh pub.sh.x pub.sh.x.c [user1@shiraz test]# ./pub.sh.x Hello World
这两个新文件,以源 shell 脚本名称的 .x 和 .x.c 扩展名命名,分别是可执行文件和一个中间 C 版本。执行 pub.sh.x 时,将执行原始 shell 源代码。shc 还指定了一个 relax 选项 -r。relax 选项用于使可执行文件可移植。基本上,shc 使用 shell 解释器本身的内容,例如 /bin/sh,作为密钥。如果 shell 二进制文件发生更改,例如,由于系统补丁或将二进制文件移动到另一个系统,则 shc 生成的二进制文件不会解密也不会执行。
我使用 strings 检查了 shell 可执行文件,没有发现原始 shell 脚本的证据。我还检查了中间 C 源代码,并注意到它以加密的八进制字符存储 shell 脚本,如清单 2 所示。
清单 2. 原始 shell 脚本在 C 版本中变为 RC4 加密字符串。
static char text[] = "\223\004\215\264\102\216\322\060\300\070\101\217\277\161\033\130" "\217\145\370\170\106\257\176\301\057\132\172\044\217\247\276\222" "\203\076\334\201\323\107\064\334\120\132\001\241\267\052\203\216" "\116\232\156\337\121\145\235\003\156\244\142\246\117\200\206\014" "\004\153\372\152\030\262\171\275\137\342\247\367\231\315\353\151" "\264\241\230\105\344\053\034\247\342\142\156\305\327\255\036\111" "\234\061\013\355\300\336\324\257\175\124\222\044\132\040\276\067" "\007\002\371\063\021\320\060";
C 源代码还包括作为数组的密码以及其他加密字符串。因此,任何有权访问源代码的人都可以轻松解密和查看原始 shell 脚本的内容。但是,由 shc 生成的原始 shell 二进制可执行文件呢?是否有可能仅从二进制可执行文件中提取原始 shell 脚本?下一个部分将探讨此问题的答案。
我生成并审查了几个 shell 脚本的 C 源代码,以更好地理解 shell 源代码是如何加密和解密的。从根本上说,shc 使用了 1994 年 9 月 13 日发布到 Usenet 新闻组的 RC4 实现。我首先着手识别加密密钥和加密文本。objdump 实用程序为此派上了用场。bjdump 是 GNU binutils 的一部分,显示有关目标文件的信息。首先,我们使用 objdump 检索所有静态变量,因为加密密钥和加密的 shell 文本都存储在这里。清单 3 简要概述了 objdump。
清单 3. objdump 浏览目标文件以查找看起来有趣的字符串。
/usr/bin/objdump --section=.data -s pub.sh.x pub.sh.x: file format elf32-i386 Contents of section .data: 804a4e0 00000000 00000000 3ca80408 00000000 ........<....... 804a4f0 00000000 00000000 00000000 00000000 ................ 804a500 00000000 506c6561 73652063 6f6e7461 ....Please conta 804a510 63742079 6f757220 70726f76 69646572 ct your provider 804a520 00000000 01000000 00000000 00000000 ................ 804a530 00000000 00000000 00000000 00000000 ................ 804a540 e554f49f 93dcd6dc bb0bdc9b ad60edd0 .T...........`.. 804a550 7a9beb67 60277cb2 dd9e0886 0797aeec z..g`'|......... 804a560 eba28b7e 7e615a3a 6d37d51a 97c2ea11 ...~~aZ:m7...... ...
清单 3 中输出的第一列以十六进制指定起始地址,后跟接下来四列中存储的数据。最后一列表示以可打印字符存储的数据。因此,在输出的前四列中的某处是构成加密密钥(密码)和加密 shell 脚本的字符数组。比较原始 C 源代码和清单 3,您可以看到密码最有可能从地址 0x804a540 开始。在比较其他可执行文件后,我确定以“Please contact your provider”文本开头的零之后的第一个地址通常是起始地址。要检索这些数组,例如清单 2 中描述的数组,我们还需要查看反汇编代码。我们在这里再次使用 objdump,但这次使用 -d 选项进行反汇编,如清单 4 所示。
清单 4. 的输出objdump -d pub.sh.x显示查找加密脚本所需的信息。(括号中的行是添加的。)
8048e52: 68 28 01 00 00 push $0x128 (Length of encryption key) 8048e57: 68 40 a5 04 08 push $0x804a540 (Key address) 8048e5c: e8 17 fb ff ff call 0x8048978 8048e61: 83 c4 10 add $0x10,%esp 8048e64: 83 ec 08 sub $0x8,%esp 8048e67: 6a 08 push $0x8 (Length of shll) 8048e69: 68 72 a6 04 08 push $0x804a672 (shll address) 8048e6e: e8 a0 fb ff ff call 0x8048a13 8048e73: 83 c4 10 add $0x10,%esp 8048e76: 83 ec 08 sub $0x8,%esp 8048e79: 6a 03 push $0x3 (length of inlo) 8048e7b: 68 8a a6 04 08 push $0x 8048e80: e8 8e fb ff ff call 0x8048a13
最后两列表示汇编指令。movl 指令用于移动数据——movl Source, Dest。当引用 C 常量时,Source 和 Dest 以 $ 为前缀。push 接受一个操作数(数据源),并将其存储在堆栈顶部。
现在我们已经掌握了 objdump 的基础知识,我们可以继续提取加密密码,最终提取 shell 代码。
在 shc 生成的中间 C 代码中,大约有九个数组由变量 pswd、shll、inlo、xecc、lsto、chk1、opts、txt 和 chk2 引用。pswd 变量存储加密密钥,txt 变量存储加密的 shell 文本。shc 将有用的信息隐藏在这些变量中的较小数组中。因此,获取实际数组涉及两个步骤。首先,确定数组的长度。其次,确定数组的起始地址。
需要详细查看 objdump 输出,以获取实际的数组长度和起始地址。我的第一个提示是查找反汇编对象代码的数据段(清单 2)中的所有地址。接下来,在清单 4 中查找所有 push 和 mov 命令。不同脚本的地址会有所不同,但是当您加密几个脚本并读取生成的 C 代码时,模式会变得熟悉。
804a540 地址似乎对应于 pswd 变量,即加密密钥。加密密钥的有用部分长度以 0x128 或十进制形式的 296 表示。同样,下一个变量 shll 和 inlo 的有用长度分别为 0x8 和 0x3,起始地址分别为 804a672 和 804a68a。这样,我们就能够获得所有九个变量的起始地址和长度。接下来,我们需要能够仅使用二进制文件作为输入来解密原始 shell 脚本。
在 shc 中,在 shell 脚本本身被加密之前,许多其他信息也被加密。此外,RC4 实现维护加密和解密每个单独信息片段之间的状态。这意味着必须维护 shc 加密和解密信息的顺序。否则会导致文本无法辨认。要提取原始 shell 脚本,我们需要执行多次解密。对于此步骤,我编写了一个名为 deshc 的小程序,使用了其中一个中间 C 文件中的现有代码。该程序读取两个文件作为其输入:二进制可执行文件和一个指定数组长度和地址的输入文件。deshc 执行以下四个步骤
读取二进制可执行文件。
从反汇编输出中提取数据段。
根据输入文件检索各个数组。
按顺序解密各个数组,以便维护 RC4 状态。
根据 objdump 输出,我得出了 pub.sh.x 可执行文件的以下数组长度和地址
pswd 0x128 0x804a540 shll 0x8 0x804a672 inlo 0x3 0x804a68a xecc 0xf 0x804a68e lsto 0x1 0x804a6a4 chk1 0xf 0x804a6a6 opts 0x1 0x804a6be txt 0x76 0x804a6e0
所有这些参数都用于 deshc 的输入文件中,然后 deshc 解密并打印原始 shell 脚本。
Nalneesh Gaur,CISSP,ISAAP,在 Diamond Cluster International 担任 BS7799 首席审核员。