Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2021-4034-CTF-writeup — CTF pwn 挑战赛题解,通过堆操作利用 CVE-2021-4034 (pkexec),包含基于 Ghidra 的逆向工程以及自定义 shelly.so 辅助二进制文件。 | Kitploit
工具/GitHubGitHub/wechicken456/cve-2021-4034-ctf-writeup
漏洞利用逆向工程CTF学习与教育二进制利用实验室与实践
GitHubwechicken456/cve-2021-4034-ctf-writeup

CVE-2021-4034-CTF-writeup

CTF pwn 挑战赛题解,通过堆操作利用 CVE-2021-4034 (pkexec),包含基于 Ghidra 的逆向工程以及自定义 shelly.so 辅助二进制文件。

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
查看仓库
2112年前尚未审核

CVE-2021-4034-CTF-writeup

这是一个我用 C 语言编写的 CTF pwn 挑战,要求用户利用 CVE-2021-4034 漏洞。玩家会在此仓库的 challenge 目录中获得 2 个二进制文件。chal 二进制实现了该 CTF 挑战,而 shelly.so 是一个辅助二进制。

如何模拟此挑战

在撰写本文时,Dockerfile 尚未完成。Dockerfile 是现场 CTF 中部署此挑战所必需的,但在本地并非必需:你仍然可以通过设置用户权限并安装易受攻击的软件包来在本地模拟此挑战,具体步骤如下:

  1. 为了成功利用此漏洞,不在 Linux 机器上的玩家应首先安装 Linux 虚拟机。然后,你需要安装一个易受攻击的内核。相关操作说明:[https://askubuntu.com/a/700221]
  2. 安装软件包 libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1。
  3. 在当前目录中创建一个属于 root:root 的 flag.txt 文件。
  4. 创建一个非特权用户,然后切换到该用户。
  5. 将 challenge 文件夹中的文件下载到当前目录。
  6. 以非特权用户身份运行 chal。

盲分析

运行 chal 二进制可以让我们大致了解该二进制的作用:``` WELCOME TO THE HUB CTRL+ALT+DELICIOUS We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte

  1. ENTER THE HUB
  2. QUIT 1 Order number: 0x7ffde93681f0 Enter your name: tin
  3. ADD NEW ORDER
  4. EDIT ORDER
  5. SHOW ORDER
  6. CANCEL ORDER
  7. CHECKOUT
  8. DONE 1 Pick your bread: aaaa Select your spread: bbbb Choose your veg: cccc Slam your meat & egg: dddc Any side notes for the cook? 0000
  9. ADD NEW ORDER
  10. EDIT ORDER
  11. SHOW ORDER
  12. CANCEL ORDER
  13. CHECKOUT
  14. DONE 1 Pick your bread: AAAA Select your spread: BBBB Choose your veg: CCCC Slam your meat & egg: DDDD Any side notes for the cook? 1111
  15. ADD NEW ORDER
  16. EDIT ORDER
  17. SHOW ORDER
  18. CANCEL ORDER
  19. CHECKOUT
  20. DONE 3 Enter order index: 0 aaaa, bbbb, cccc, dddc, 0000
  21. ADD NEW ORDER
  22. EDIT ORDER
  23. SHOW ORDER
  24. CANCEL ORDER
  25. CHECKOUT
  26. DONE 3 Enter order index: 1 AAAA, BBBB, CCCC, DDDD, 1111
  27. ADD NEW ORDER
  28. EDIT ORDER
  29. SHOW ORDER
  30. CANCEL ORDER
  31. CHECKOUT
  32. DONE 4 Enter order index: 1
  33. ADD NEW ORDER
  34. EDIT ORDER
  35. SHOW ORDER
  36. CANCEL ORDER
  37. CHECKOUT
  38. DONE 3 Enter order index: 1 Invalid index!
  39. ADD NEW ORDER
  40. EDIT ORDER
  41. SHOW ORDER
  42. CANCEL ORDER
  43. CHECKOUT
  44. DONE 5 /notes ./tin/notes
  45. ADD NEW ORDER
  46. EDIT ORDER
  47. SHOW ORDER
  48. CANCEL ORDER
  49. CHECKOUT
  50. DONE 6
  51. ENTER THE HUB
  52. QUIT 2 Come again :)
在 `Add` 函数中调整输入大小,以及调整 `Cancel` 函数的索引,都没有给我们带来什么特别的结果(没有溢出或段错误)。不过,有一些有趣的地方:
  - 订单似乎被放入一个列表(可能是链表)中,并且是从 0 开始索引的?
  - 这里有 `Order number: 0x7ffde93681f0`,似乎打印的是栈上的某个地址?
  - 另外,程序会创建一个以我们输入名称命名的目录,同时还有 3 个可执行文件,其中一个是给定的 helper 二进制文件:```sh
peasant@Tin-VM:~/Desktop$ ls
chal  chal.c  Dockerfile shelly.so  solve.py  tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf     5 Feb  4 14:33 notes
-rwxrwx--- 1 peasant vboxsf    20 Feb  4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb  4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes 
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe 
aaaa
bbbb
cccc
dddc

可执行文件包含我们的输入。


Ghidra 分析

你可以尝试使用其他函数,并希望能碰到一些 bug(这很有可能),但我要直接切入正题,在 Ghidra 中打开该程序。

对比程序运行期间出现的字符串与 Ghidra 中存在的字符串,我们可以将一些 FUN_* 函数重命名为熟悉的名称:```C undefined8 main(void)

{ int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");

该 `printf("Order number: %p\n",&local_18);` **打印出栈上某个局部变量的位置。**

在 gdb 中快速检查表明,泄露的地址是指向常量字符串 `/recipe` 的指针的地址:```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤  x/gx 0x7fffffffdfc0
0x7fffffffdfc0:	0x0000555555559020
gef➤  x/s 0x0000555555559020
0x555555559020:	"/recipe"

我们看到一个 mkdir 调用,它在当前目录中创建一个以我们输入名称命名的目录。

这些与我们运行程序时的观察一致。然后,它在调用 main_menu 函数之前初始化了一些变量,其大致内容如下:```C while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { print("1. ADD NEW ORDER\n"); print("2. EDIT ORDER\n"); print("3. SHOW ORDER\n"); print("4. CANCEL ORDER\n"); print("5. CHECKOUT\n"); print("6. DONE\n"); __isoc99_scanf(&DAT_001030c5,&local_40); getc(stdin); if (local_40 != 1) break; add_order(); } if (local_40 != 2) break; edit_order(); } if (local_40 != 3) break; show_order(); } if (local_40 != 4) break; cancel_order(); } if (local_40 != 5) break; checkout(); } if (local_40 == 6) break; if (local_40 == 0x539) { print( "\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n" ); fgets(inp,0x20,stdin); getrandom(random-bytes,0x10,0); for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) { if (inp[local_3c] != random-bytes[local_3c]) { print("...Nuh Uh!...\n"); /* WARNING: Subroutine does not return */ exit(0); } print("...Ooh Yes.. sCruMpTioUs..."); } print("Fine... I'll give you a taste.\n"); FUN_00101504(); }

如果你不熟悉 REV,这就是 C 语言中 `switch` 语句的反编译结果。有一个有趣的选项,即 `0x539`。  它允许玩家猜测 `0x10` 个随机字节。如果所有字节都相等,那么它会调用 `FUN_00101504();`,而该函数会调用 `system("cat flag.txt");`。否则,程序退出。

然而,暴力破解 16 个随机字节等同于尝试所有 256**16 = 340282366920938463463374607431768211456 种可能性。这个嘛,祝你好运 lol。

即使你遍历了所有可能性,程序仍然没有特权,因此它无法读取 flag。这个 `cat flag.txt` 语句是故意的,不仅是为了诱骗没有经验的玩家选择 `0x539` 这个菜单选项,也是为了防止玩家直接调用这个函数来读取 flag,我们稍后会深入探讨这一点。
***
## 添加```C
  int iVar1;
  undefined8 *puVar2;
  void *pvVar3;
  undefined8 *ptr2;
  
  if (order_cnt < 10) {
    puVar2 = (undefined8 *)malloc(0x30);
    print("Pick your bread: ");
    readline(puVar2 + 1,8);
    print("Select your spread: ");
    readline(puVar2 + 2,8);
    print("Choose your veg: ");
    readline(puVar2 + 3,8);
    print("Slam your meat & egg: ");
    readline(puVar2 + 4,9);
    iVar1 = order_cnt;
    pvVar3 = malloc(0x30);
    *(void **)(&ptr_array + (long)iVar1 * 8) = pvVar3;
    *puVar2 = *(undefined8 *)(&ptr_array + (long)order_cnt * 8);
    print("Any side notes for the cook? ");
    readline(*puVar2,0x30);
    puVar2[5] = 0;
    if (DAT_00105140 != (undefined8 *)0x0) {
      for (ptr2 = DAT_00105140; ptr2[5] != 0; ptr2 = (undefined8 *)ptr2[5]) {
      }
      ptr2[5] = puVar2;
      puVar2 = DAT_00105140;
    }
    DAT_00105140 = puVar2;
    order_cnt = order_cnt + 1;
  }
...

如果你觉得这些变量看起来不太容易读,那是因为我花了一些时间把它们重命名成这样,你在逆向工程时也应该这样做,以便跟踪各个变量的用途。好了,让我们进入要点:

  • puVar2 是一个 0x30 大小的 malloc 块。
  • 第 0 个字段是 puVar3 指针,长度为 8 字节,指向当前 chunk 的 notes。
  • 接下来的 4 个字段每个都是 8 字节长,不过我们可以向第 4 个字段读入 9 个字节,因此可以向第 5 个字段溢出 1 个字节。
  • 但是,puVar2[5] = 0; 这一行仍然会把我们溢出的 1 个字节改写为 NULL。
  • 然后,如果全局变量 DAT_00105140 == 0,我们就直接将它设置为新 chunk,所以这可能是指向链表头部的 head 指针?
  • 否则,我们会循环遍历当前链表直到最后一个 chunk,并将其第 5 个字段设置为新 chunk。=> 第 5 个字段是指向链表中下一个 chunk 的指针。
下载工具