QNX(Quick UNIX)是一个unix家族的实时操作系统,***发行于1980年,2010年被黑莓公司收购。QNX通常用于嵌入式系统中,在企业服务器领域并不常见。参考资料[1]的博客中有一些QNX安全评估和渗透测试的信息。
QNX是一种商用的类Unix实时操作系统,遵从POSⅨ规范,目标市场主要是嵌入式系统。QNX成立于1980年,是加拿大一家知名的嵌入式系统开发商。
QNX的应用范围极广,包含了:控制保时捷跑车的音乐和媒体功能、核电站和美国陆军无人驾驶Crusher坦克的控制系统,还有RIM公司的BlackBerry PlayBook平板电脑。
如果你有幸在渗透测试或研究中接触过QNX主机,你可能会意识到/etc/shadow文件下的shadowed密码使用了一种不常用的格式。本文中的这些经验能够帮助你理解其原理,并且告诉你如何爆破这种格式的hash值。
可输出的hash格式
QNX系统将/usr/bin/passwd中的二进制以可输出的格式生成到/etc/shadow文件中。它包含了生成和解析这些hash值的所有逻辑数据,这是逆向这个格式的***资源。
这个文件支持好几种hash加密方法:MD5,SHA-{256,512},以及QNX遗留的不安全加密实现(参见CVE-2000-0250)。他们分别命名为md5_crypt,sha2_crypt,qnx_crypt。
另外,其同时还支持解析明文密码的plain_crypt方法,但是这个你需要手动修改/etc/shadow。 有趣的是,SHA-1支持比较老的版本,但是由于没有链接库调用路径,所以没有办法使用。
QNX Neutrino 6.6.0默认的hash方法是SHA-512,1000轮计算,并附带16字节的盐值(参考[2])。 用SHA-512对用户账号密码附加8字节的盐值做1000轮的计算生成的结果如下:
这是MD5和SHA-{256,512}的通用格式。QNX的hash加密方法,格式上很像传统的Linux DES加密字符串。
1.Shadowed密码文件用冒号(:)分割得到下面接个段: 2.username – 用户名 3.@S,100@386d...truncated...da5d@129b6761 –可输出的hash字符串(根据使用hash方法的不同而不同) 4.1448613322 – 设置密码时产生的时间戳 5.0 – 未知 6.0 – 未知
面我还未找到***两个值是用来干嘛的,但这两个值总为0也许跟不可使用的账户有关。
可输出的hash字符串又被@符号分割成下面这几个段:
S,100 –前面表示使用的hash方法,后面表示加密计算轮数 S -- SHA-512 s -- SHA-256 m -- MD5 p – 明文密码 386d...truncated...da5d – 使用hash方法计算出的16进制结果 129b6761 – 16进制盐值
Hash密码例子
下面所有的例子都是密码的hash值
SHA-512加密, 1000轮, 16字节盐值
username:@S@60653c9f515eb8480486450c82eaad67f894e2f4828b6340fa28f47b7c84cc2b8bc451e37396150a1ab282179c6fe4ca777a7c1a17511b5d83f0ce23ca28da5d@caa3cc118d2deb23:1448585812:0:0
SHA-512加密, 1000轮, 8字节盐值
username:@S@386d4be6fe9625c014b2486d8617ccfc521566be190d8a982b93698b99e0e3e3a18464281a514d5dda3ec5581389086f42b5dde023e934221bbe2e0106674cf7@129b6761:1448585864:0:0
SHA-256加密, 1000轮,16字节盐值
username:@s@1de2b7922fa592a0100a1b2b43ea206427cc044917bf9ad219f17c5db0af0452@36bdb8080d25f44f:1448585954:0:0
MD5加密, 1000轮, 16字节盐值
username:@m@bde10f1a1119328c64594c52df3165cf@6e1f9a390d50a85c:1448585838:0:
内部二进制代码
从QNX Neutrino 6.6.0系统得到的/usr/bin/passwd 二进制并不很令人兴奋。 这是一个32位的可执行文件,没有剪去符号表和libc,ld-linux,linux-gate的动态链接,能够找到shadowed密码文件的所有hash加密实现。
文件/etc/default/passwd会影响二进制的行为,只有个QNXCRYPT指令存在于文件中时QNX加密方法才会被使用。另外一些有趣的指令,比如STRICTPASSWORD确保密码使用至少两个字符集、NOPASSWORDOK允许使用空白密码。
函数gensalt使用系统时间初始化一个随机算法来生成一个随机值,如果想要深入挖掘可以使用initstate,setstate,random,srandom函数。我没有很详细看这几个函数,但是聚合熵少于8字节随机操作会失败。 该盐值是16进制字符串,如果设置为16字节就是16个字符。
qnx_crypt 已经有说明文档,并且没有修改。(参考文档[3])
md5_crypt 使用函数MD5Init, MD5Update, MD5Transform。
sha2_crpyt 使用函数shaXXX_init, shaXXX_update,shaXXX_done,XXX指的是位,如512。
QNX的hash函数是按位偏移的。 Hash函数初始化之后,更新过程如下所示:
digest = update(salt), update(password) * rounds, update(password)
***一次轮数置为0时,是使用密码来更新hash函数的。 所以摘要被计算出来的时候,1000轮的设置,其实是被计算了1001轮。 除此之外,其他都是标准的。
QNX 密码hash值爆破
John the Ripper (即使用了jumbo补丁)和其他通用的工具都不支持QNX shadowed密码的hash格式。
我尝试用python的hashlib和passlib模块重新实现hash函数的逻辑。但是我得到的输出跟/usr/bin/passwd产生的二进制结果无法匹配。 所用方法的实现都是一致的,所以我意识到QNX的hash函数有一些特有的实现。这是QNX自己实现的代码,并不依赖于通用的扩展库, 比如OpenSSL。
我并不是加密专家所以我并没有花很多的时间去逆向hash函数,但是我看了SHA-2的参考文档并且似乎QNX就是按照这个方案来实现的。输出的不同可能是由于他们内部实现的一个奇怪的调整导致的。
如果你想暴力破解hash值,你可以直接在GDB调试中调用它。另一个优雅的实现就是使用dlopen(3) 和dlsym(3)去从C的封装调用这个函数。
Breakpoint 1, 0x080493c1 in main () (gdb) call sha2_crypt(512, "password", "abcd1234abcd1234", "1000") $1 = 134559360 (gdb) x/s $1 0x8053680 : "@S@1030f372de34b8caac99b481d81ad9b57b923b385edcd3ed84f6721192f5238f34aba739e1d124919bd85c8efe13948593a6b691d8b41c1be5bc9b3906577f5d@abcd1234abcd1234"
参考资料:
[1] The Pentesting QNX Neutrino RTOS blog post from FishNet Security was the best information I could find on QNX for security assessments or penetration tests.
[2] The defaults can be found from the /usr/bin/passwd binary itself or in the online QNX development references.
[3] The qnx_decrypt.c code updated by SilentDream is the best reference for understanding QNX crypt hashes.