常见好用的堆栈技巧——比赛专用
2023-3-25 10:10:0 Author: xz.aliyun.com(查看原文) 阅读量:37 收藏

翻译链接:heap-tricks-never-get-old-insomnihack-teaser

文章分类:二进制漏洞分析

Synacktiv红队在上周末的Insomni'hack比赛,以280名队伍第九名完结。其中有一个挑战非常有趣,并且教会了我一些技巧和方法,所以我决定写一篇详细的博客。在这篇文章中,我会努力充分的解释解决这个问题的思考过程,绝不仅仅是一般的方法。希望你能享受这个阅读,最后的exp.py放在文末附录。

二进制文件和libc文件都是沿用pwn的方法进行使用。

1.初始化安装步骤

从保护方法来看,ontestament.bin文件有很好的保护措施,包括RELPO保护,NX位保护,PIE保护

$ checksec ontestament.bin
[*] '/home/bak/onetestament/ontestament.bin'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

提供的libc文件是与源文件剥离的,可以方便的使用debug符号重新得到等效项。辛运的是,pwninit工具就是这么做得,修补二进制文件,使用其提供的libc链接,而不是使用系统链接。

libc链接可以使用ldd工具来验证:

$ ldd ontestament.bin_patched
    linux-vdso.so.1 (0x00007ffc057eb000)
    libc.so.6 => ./libc.so.6 (0x00007f84a2ec8000)
    ./ld-2.23.so => /lib64/ld-linux-x86-64.so.2 (0x00007f84a3499000)

同时,libc的版本号是多少呢?

$ ./libc6.so | head -n 1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et al.

如果你对常见heap堆技巧非常熟悉,那么你会知道知道libc的版本非常重要。

事实上,heap的内存管理随着时间的已经发展了很长一段时间了。保护措施的出现,内存结构的改变,他们的工作方式也在改变。这也是为什么基于堆的攻击常常影响的是libc的特定的版本。如果你想看更多这方面的资料,可以看看A repository for learning various heap exploitation techniques.

在我的印象中,Glibc 2.23开始于2016年2月份,他已经是非常老的了。在开始挖漏洞之前,有一件非常有趣的事情需要做,那就是看看下一个版本新版本的libc中有什么安全修复措施和相关安全保护措施。

2.逆向工程程序漏洞

程序特征识别

这个程序的运行结果是非常的直接。列出所有的堆技术,你可以创建对象,编辑对象,查看对象,删除对象。至少这也是我所想的,当我第一次看到这个程序运行的时候。

$ ./ontestament.bin

==========================
     ✝ OneTestament ✝      
==========================
1. My new testament
2. Show my testament
3. Edit my testament
4. Delete my testament
5. Bye!
Please enter your choice:

在程序运行之前,一个alarm(20)的调用产生了。这会在20秒之后触发SIGALRM信号,停止程序。为了避免这个令人烦恼的行为,可以修补二进制文件(nop对alarm的调用)或者简单的在gdb输入以下命令:

pwndbg> handle SIGALRM ignore
Signal        Stop      Print   Pass to program Description
SIGALRM       No        Yes     No              Alarm clock

让我们迅速开始查看所有的重要函数。

创建程序

函数的伪代码如下:

首先,我发现了程序只允许10次分配,全局变量(此处变量名是nb_testaments)在每次新分配后都会递增。

可用的大小包含:0x18, 0x30, 0x60 and 0x7c 字节,这样可以方便我在快速bins和未排序bins中释放数据块chunks。提醒一下,每个大于0x58字节的数据块在释放后都会放入未排序的容器中。

数据块chunks由calloc()函数负责分配,关于calloc()函数与malloc()函数的主要区别是后者对分配的内存区域执行memset(mem,0sz)

之后,testament将用户指定的数据填入。与一般所想不同的是,fill_testament()函数是安全的,这里不再详细给出缘由。

另一个有趣的地方是,testament指针和大小都存储在全局变量中,即.bss段。

细心地读者发现了我调过了read_input()函数,他的伪代码如下:

__int64 read_input()
{
  int v2; // [rsp+Ch] [rbp-4h]

  read(0, nptr, 5uLL);
  v2 = atoi(nptr);
  if ( v2 < 0 )
    return 0;
  else
    return (unsigned int)v2;
}

有5个字符是从用户数据读取的,并且存储在了全局变量的char nptr[4]中。等等,有一个1个字节的溢出?是的,非常正确,让我们记住这一点,看看后面会有什么用处。

展示程序

在每次学习heap堆技巧是,我有一个方法可以泄露地址,显示对象的内容:

int show_testament()
{
  int index; // [rsp+Ch] [rbp-4h]

  index = init_rand(5, 0);
  return printf((&random_sentences)[index]);
}

random_sentences是一个数据,调用这个函数时会随机产生一个句子。

等一下,这是隐藏的hint吗?可能是的,我先来分析init_rand()看看。

__int64 __fastcall init_rand(int a1, int a2)
{
  unsigned int ptr; // [rsp+14h] [rbp-Ch] BYREF
  FILE *stream; // [rsp+18h] [rbp-8h]

  stream = fopen("/dev/urandom", "r");
  fread(&ptr, 4uLL, 1uLL, stream);
  fclose(stream);
  srand(ptr);
  return (unsigned int)(rand() % a1 + a2);
}

看来这只是一个恶搞


文章来源: https://xz.aliyun.com/t/12348
如有侵权请联系:admin#unsafe.sh