前些天我遇到一个程序,它允许我从预先打包的查询集里选择,然后通过与服务器通信来获取所选查询的答案。 当我意识到该程序从未要求输入密码进行服务器认证时,不禁产生了疑虑。原来密码被直接嵌入了程序中。作者解释说这样做没问题,理由有二:1) 这是二进制文件,无人能读取内容;2) 即使能读取文件,也无法找到密码字符串。
这两种理由都不成立,我将通过本文来证明这一点。我的示例程序仅在终端输出"密码",但它与实际生产程序的唯一区别在于:恶意用户需要搜索的字符串数量。
#include <stdio.h>
#include <string.h>
main ()
{
char password [9] = {"secret"};
printf ("This is the password: %sn", password);
}
|
| 图1 – 带内嵌密码的基本程序 |
执行时显示
x1 这是密码:secret 准备就绪 14:12:16 |
| 图2 – 基本程序的执行 |
要显示程序模块中的字符串,恶意用户可使用位于>system>gnu_library>bin目录下的strings命令。参数-n5将输出限制为5个字符或更长的字符串。 为简化展示,我已截断输出内容(......),但可见密码出现在最前面的字符串之一。当密码紧邻"login"、"password"等关键词,或"admin"、"root"、"sql"等可识别的用户ID时,搜索过程将更为便捷。
>system>gnu_library>bin>strings -n5 x1.pm
bind, Release 17.1.beta.be
phx_vos#
Noah_Davids.CAC
预发布版
密码为:%s
secret
s$start_c_program
_preemption_cleanup
_exit.
. . . .
|
| 图3 – 使用GNU strings命令查找程序模块中所有长度超过4个字符的字符串 |
如果字符串命令不可用,恶意用户只需显示程序模块即可。我怀疑我们都曾无意中这样操作过——虽然操作方式不太优雅,但确实能显示密码。我再次截取了输出内容,你依然能看到密码。
d x1.pm
%phx_vos#m16_mas>系统管理员>Noah_Davids>x1.pm 11-01-13 14:13:59 mst
`00`01`00`1Abind, Release 17.1.beta.be`00`00`00`00`00`00`00`04ctp
`00`00`00`00`0
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`0
0phx_vos
+#`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`
00`00Noa.
. . .
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`C
D'`8B`C0
+`00`00`00`00这是密码:%s
`00`00`00`00`00`00`00secret`00`00`00`00`00`00`00`04`B0`00`00`00`00`01`00
`00`00,`
+`00`00`04`00`00`00`03@`00`FF`F8`FF`FE`00`04main`00`00`83`EC`2C`C7D$$`00.
. . .
|
| 图4 – 显示用于查找密码的程序模块 |
在程序中嵌入密码有几种替代方案。
密码可存储于文件中,程序可读取该文件。仅限"需执行"人员获得文件读取权限,其余人员均无权访问。当然,"需执行"未必等同于"需知晓密码"。此外,始终存在他人误改访问权限的风险。
另一种方案是将密码字符串保存在程序中,仅限制对程序的访问权限。但这种方案同样存在"访问列表易被篡改"的问题。若该程序与其他可被任意用户运行的程序存放在同一目录下,访问列表被修改的概率将随之增加。
最后,您可以在程序模块中保留密码,但需对字符串进行某种混淆处理。以下程序展示了一种实现此目的的简单方法。
#include <stdio.h>
#include <string.h>
main ()
{
char obfuscate [9] = {0x0c, 0x81, 0xe2, 0x94, 0xe6, 0x9c};
char key [9] = {127, 228, 129, 230, 131, 232, 133, 234, 135};
char password [9];
int i;
for (i = 0; i < strlen (obfuscate); i++)
password [i] = obfuscate [i] ^ key [i];
password [i] = 0x0;
printf ("This is the password: %sn", password);
}
|
| 图5 – 包含混淆密码字符串的程序 |
如你所见,它所做的只是将混淆字符与另一组密钥字符进行异或运算来生成密码。运行该程序会产生与第一个程序相同的输出结果。
x2 这是密码:secret 准备就绪 14:12:51 |
| 图6 – 混淆程序的执行过程 |
但字符串搜索并未显示任何类似密码的内容
>system>gnu_library>bin>strings -n5 x1.pm bind, Release 17.1.beta.be phx_vos# Noah_Davids.CAC 预发布版 密码为:%s s$start_c_program _preemption_cleanup _exit. . . . |
| 图7 – strings命令不再显示密码 |
虽然文件显示会呈现混淆的密码字符和密钥字符,但你必须精确知道在哪里查找才能找到它们,同时还需了解它们如何组合形成密码。
d x2.pm
%phx_vos#m16_mas>系统管理员>Noah_Davids>x2.pm 11-01-13 14:15:27 mst
. . . .
+`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`00`CD'`8B`
C0`00`0
+0`00`00这是密码:%s
`00`00`00`00`00`00`00`0C`81`E2`94`E6`9C`00`00`00`00`00`00`00`00`00`00`7F
`E4`81`
+E6`83`E8`85`EA`87`00`00`00`00`04`B0`00`00`00`00`01`00`00`00`00`00`00`0
4`01`B0
+`00`03@`00`FF`F0`00`08`FF`FE`00`04main`00`00`83`EC`89$X`89t$T`89|$P`C
7D$L`00
|
| 图8 – 程序模块显示界面不再显示可识别的密码 |
这仍非完美解决方案,因为程序模块仍可被反编译以解析密码生成机制。不过反编译程序所需的技术复杂度远高于单纯显示程序模块。
对于将混淆后的密码字符串存入文件,再通过访问控制列表保护该文件的做法,我存在矛盾心理。一方面,若密码需要变更,这种方案远优于将混淆字符串直接嵌入程序。但另一方面,即便文件中还包含其他配置选项,将密码字符串存入文件仍会减少恶意用户需要排查的潜在密码组合数量。 当然,攻击者仍需先获取文件访问权限,继而破解混淆技术并推测密钥。
最后一点,我是如何生成混淆的密码字符的?异或运算的妙处在于((A 异或 B) 异或 B) 等于 A,因此我编写了一个简短程序:将原始密码与密钥字符进行异或运算,然后输出结果。
#include <stdio.h>
#include <string.h>
main ()
{
char password [9];
char key [9] = {127, 228, 129, 230, 131, 232, 133, 234, 135};
char newPW [9];
int i;
strcpy (password, "secret");
for (i = 0; i < strlen (password); i++)
{
newPW [i] = password [i] ^ key [i];
printf ("%xn", newPW [i]);
}
}
|
| 图9 – 生成混淆密码的程序 |
