linux用户层pwn
基础指令
打通了cat flag没有该文件,find / -name flag会列出flag所在地方,cat一下就好了
当文件中没有/bin/sh时,可以用sh来替代,在linux中前者时文件,后者时环境变量
IDA:
shift+F12;ctrl+f
jg大于则跳转,jl小于则跳转,jump无条件跳转
encode与decode区别
gdb:
ctrl+c,p &printf,
直接查看该地址所存储的东西:x/x addr,
查看got表:got
disass main:查看main函数的汇编
查看事务:
1 | gef➤ x/a 0xffffd0d0 |
查看寄存器: info registers
接收数据:
64位canary接受:因为正常canary的末位为”\x00”
1 | canary=u64(p.recv(7).rjust(8,b'\x00')) |
64位的堆接受某个展示的数据
1 | free_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) |
32位:
1 | leak = u32(r.recvuntil('\xf7')[-4:])-libc.sym['printf'] |
若接受的地址为Buffer address: 0x7ffd4b9c3e20\n,则addr= int(r.recvline()[14:-2], 16) 或int(r.recv(7),16)
或者r.recvuntil(b”0x”),addr=int(r.recv(14),16)

一般got表只接受前6位,即
1 | addr=u64(r.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) |
注意ljust与rjust区别
基础
got表中存储的为真实地址,指其所指向的为函数真实地址
汇编指令学习

栈迁移
当不存在栈溢出时,通过栈迁移来做法
变量是根据rbp来寻址的
leave指令的本质其实就是:
mov esp,ebp
pop ebp
ret指令的本质其实就是:
pop eip
1.进行任意地址改写
改变rbp的位置
图中50h值得是十六进制


由图一可知,只能读取0x58大小,0x50+0x8=0x58,因此,最多只能改写到rbp部分,返回地址写不到,所以不能进行溢出
由图二可知,v4可通过scanf直接读取,又因为v4的偏移为rbp-0x4,所以我们把rbp的内容改为passwd+0x4,这样,当给v4赋值时,会将rbp-0x4的部分写为v4的值,又因为passwd在rbp-4的位置,这样也顺便给passwd赋值
1 | exp |
2.增加溢出长度
先在一个位置布置好rop链条,然后把程序的执行流迁移到我们布置好rop链条的位置,让程序执行流执行踏入我们布置好了的地方

若read读入大小足够,exp:
payload=b'a'*padding+p64(pop_rdi)+p64("bin_sh_addr")+p64(ret)+p64(system)
原理:

第一次leave:
第一次ret:
第二次leave:
1 | `exp:` |
题目:buuctf第26题
输出的内容不是ebp的地址,只是ebp的内容,通过调试可知二者相差0x10
exp:
1 | `from pwn import*` |
orw沙盒
1 | 检验:seccomp-tools dump ./文件名 |
32位传参顺序:ebx,ecx,edx,esi,edi,ebp
64位使用寄存器传参,分别用rdi,rsi,rdx,rcx,r8,r9作为第1-6个参数。rax作为返回值
1 | #sys_open('flag',0,0) |
1 | #sys_read(fd,buf,0x30)用到了ebx,ecx,edx |
1 | #sys_write(1,file,0x30) |
1 | from pwn import * |
1 | from pwn import * |
题目来源:pwnable_orw 1
栈溢出
ret2libc
ASLR 共享libc,堆,和栈的地址进行随机化 

bin_sh=base+next(libc.search(b”/bin/sh”))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
system(“sh”)=system(“/bin/sh”)
1.ASLR:
泄露puts类的,32位,直接
1 | `payload=b'a'*padding+p32(puts_plt)+p32(main_addr)+p32(puts_got)`, |
泄露write类的,32位,直接
1 | `payload=b'a'*padding+p32(write_plt)+p32(system)+p32(1)+p32(write_got)+p32(4)` |
64位
1 | `payload=b'a'*padding+p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(write_got)+p64(pop_rdx_ret)+p64(8)` |
典中典:
1 | `from pwn import *` |
!!!
1 scanf读入“+,-”好之类的不会写入内存
2 注意读入的数是unsigned还是signed
3 64位shellcode需要标住环境 context.arch = “amd64” ,没有的话就默认32位
4 若存在后门函数且溢出的字符不够,可以只改变部分的返回值,如p8(0x88)就是将返回地址的低8位改为\x88
5 如果题目是v==0.1触发shell,且其在我们要覆盖的东西范围之内,那么我们就将该值转为0.1的64位0x3FB999999999999A,即p64(0x3FB999999999999A),read通常要转为64位or32位,scanf(“%s or %d”,v)–p32/p64,scanf(“%f”,v)–(b’0.1’)
ret2shellcode
32位不强制要标明,64位要。
若要绕过某些检查后执行shellcode,可在前面加上”\x00b\x22”这样可以绕过,为什么要加上\x22,为了防止shellcode没有对齐被错误解析,还有一个原因是这个汇编指令不会改变寄存器的值
手搓32位:
1 | shellcode =''' |
1 | shellcode_s =''' |
1 | jmp esp//使程序跳转到esp所指向的位置,简单说就是p32(jmp esp)+asm(自创汇编) |
逆序输出shellcode:
1 | shellcode = asm(shellcraft.sh())[::-1] |
amd64, linux可见字符shellcode 为’
1 | Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t |
例子:buu ciscn_2019_s_9 1
ropchain
ROPgadget –binary 文件名 –ropchain
1 | funcadd(调用地址)+retadd(返回地址)+agrsadd(调用参数地址) |
中级rop
前提回顾:
*write触发: write(int fd, const void buf, size_t count) rdi=1,rdx=8,rsi=泄露地址;

32位write输出的位置在esp开始
*read触发条件: read(int fd, void buf, size_t count) ,rdi=0,rsi=buf地址,rdx=读入字节的大小,即size,还有rax存放其系统调用号——0,rip存放read函数地址。
syscall32位:eax=0xb,ebx=”/bin/sh\x00”,ecx=0,rdx=0,int 0x8地址
execve64位:rax=0x3b,rdi=“/bin/sh\x00”
系统调用查找64位: Linux Syscall64 Reference ;32位: Linux Syscall Reference
长句解释
1 | payload = b'a'*(0x1C+4) + p32(read_plt) + p32(pop_3) + p32(0) + p32(bss) + p32(0x9) + p32(pop_eax) + p32(eax) + p32(pop_3) + p32(0) + p32(0) + p32(bss) + p32(int_80) |
先把栈布局好,然后会跳转到read处,又因为栈布局到int_80,所以输入的”/bin/sh\x00”为read的参数
第一个pop_3第一种解释是将三个参数导出,第二种解释就是相对应的改造其rdi,rsi,rdx
ret2csu
$$
.text:00000000004005C0 ; void _libc_csu_init(void)
.text:00000000004005C0 public libc_csu_init
.text:00000000004005C0 libc_csu_init proc near ; DATA XREF: _start+16o
.text:00000000004005C0 push r15
.text:00000000004005C2 push r14
.text:00000000004005C4 mov r15d, edi
.text:00000000004005C7 push r13
.text:00000000004005C9 push r12
.text:00000000004005CB lea r12, frame_dummy_init_array_entry
.text:00000000004005D2 push rbp
.text:00000000004005D3 lea rbp, do_global_dtors_aux_fini_array_entry
.text:00000000004005DA push rbx
.text:00000000004005DB mov r14, rsi
.text:00000000004005DE mov r13, rdx
.text:00000000004005E1 sub rbp, r12
.text:00000000004005E4 sub rsp, 8
.text:00000000004005E8 sar rbp, 3
.text:00000000004005EC call _init_proc
.text:00000000004005F1 test rbp, rbp
.text:00000000004005F4 jz short loc_400616
.text:00000000004005F6 xor ebx, ebx
.text:00000000004005F8 nop dword ptr [rax+rax+00000000h]
.text:0000000000400600
.text:0000000000400600 loc_400600: ; CODE XREF: libc_csu_init+54j
.text:0000000000400600 mov rdx, r13
.text:0000000000400603 mov rsi, r14
.text:0000000000400606 mov edi, r15d
.text:0000000000400609 call qword ptr [r12+rbx*8]
.text:000000000040060D add rbx, 1
.text:0000000000400611 cmp rbx, rbp
.text:0000000000400614 jnz short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616: ; CODE XREF: libc_csu_init+34j
.text:0000000000400616 add rsp, 8
.text:000000000040061A pop rbx
.text:000000000040061B pop rbp
.text:000000000040061C pop r12
.text:000000000040061E pop r13
.text:0000000000400620 pop r14
.text:0000000000400622 pop r15
.text:0000000000400624 retn
.text:0000000000400624 __libc_csu_init endp
$$
**
可发现,调用rdi,rsi,rdx的寄存器为0x400600,而间接调用的是0x40061A,因此通过赋值给其他寄存器后会间接改变需要的寄存器的值
1 | def csu(rbx, rbp, r12, r13, r14, r15, last): |
write调用: csu(0, 1, write_got, 8, write_got, 1, main_addr)
read调用: csu(0, 1, read_got, 16, bss_base, 0, main_addr) ,然后执行的就是read功能,所以发送 sh.send(p64(execve_addr) + ‘/bin/sh\x00’)
execve(bss_base+8) :csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr)
SROP
寄存器设置
- rax:系统调用号(例如0表示read,0x3b表示execve)
- rdi:第一个参数(文件描述符、文件名指针等)
- rsi:第二个参数(缓冲区地址、argv等)
- rdx:第三个参数(长度、envp等)
- rip:sigreturn后的指令指针(通常指向syscall)
- rsp/rbp:如果需要控制栈流,设置栈指针
设置功能前提是要有个rax将其设为”0xf“为sigreturn系统调用”
设置read功能:
1 | frame = SigreturnFrame() |
设置execve功能:
1 | frame = SigreturnFrame() |
格式化字符串基础
基本格式如下:
$$
%[parameter] [flag] [field width] [.precision] [length] type
parameter
n$,获取格式化字符串中的指定参数
flag
field width
输出的最小宽度
precision
输出的最大长度
length,输出的长度
hh,输出一个字节
h,输出一个双字节
type
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数payload = p32(c_addr) + ‘%012d’ + ‘%6$n’
$$
程序崩溃:
直接输入 ,因为不可能每一个都对应合法地址
1 | %s%s%s%s%s%s%s%s%s%s%s%s%s%s |
泄露内存:
泄露栈内存
获取某个变量的值 %n$x得到printf的第n+1个参数的值
获取某个变量对应地址的内存
小技巧:
- 利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
- 利用 %s 来获取变量所对应地址的内容,只不过有零截断。
- 利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。
泄露任意地址内存
利用 GOT 表得到 libc 函数地址,进而获取 libc,进而获取其它 libc 函数地址
盲打,dump 整个程序,获取有用信息
写法:
1
2
3
4
5
6
7
8
9
10
11
12from pwn import *
sh = process('./leakmemory')
leakmemory = ELF('./leakmemory')
__isoc99_scanf_got=leakmemory.got['__isoc99_scanf']
print hex(__isoc99_scanf_got)
payload = p32(__isoc99_scanf_got) + '%4$s'
print payload
gdb.attach(sh)
sh.sendline(payload)
sh.recvuntil('%4$s\n')
print hex(u32(sh.recv()[4:8])) # remove the first bytes of __isoc99_scanf@got sh.interactive()
!!!
64位程序偏移量要加上5
输出在栈上的数据,直接输入”%n$s”,n为该参数举例printf输入位置的偏移,不用加上5
覆盖内存
要求:变量对应的内存可写
步骤:1.确定覆盖地址 2.找出相对偏移 3.进行覆盖
要让c等于16,c的地址占了4:
1 | payload = p32(c_addr) + '%012d' + '%6$n' |
覆写很小的地址:原先格式化字符串对应偏移为6,即aa%8占第六个,¥naa占第七个,a的地址占了第八个,所以偏移改为8:aa%8$naa占8字节,int(2)=8字节
1 | payload = 'aa%8$naa' + p32(a_addr) |
覆写超大地址: 利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节 ,%n写入4字节
起始地址0x0804A028为要修改的变量,依次覆写其小端序,如0x12345678为\x78\x56\x34\x12,pad1为\x78
1 | p32(0x0804A028)+p32(0x0804A029)+p32(0x0804A02a)+p32(0x0804A02b)+pad1+'%6$hn'+pad2+'%7$hn'+pad3+'%8$hn'+pad4+'%9$hn' |
快速计算偏移fmtarg
意会:

1 | gef➤ fmtarg 0x00007fffffffdb28 |
10-1=9,所以偏移为9
函数:fmtstr_payload
buu axb_2019_fmt32 1
1 | def fmtstr_payload(offset, writes, write_size='', numbwritten=? ) |
1 | 使用:payload1 = b'A' + fmtstr_payload(8,{printf_got:system_addr},write_size='byte',numbwritten=0xa ) |
堆基础
基础技术
申请
calloc(0x20)等效于ptr=malloc(0x20),memset(ptr,0,0x20)
realloc:
off-by-one
关键在于怎么骗,例如在roarctf_2019_easy_pwn,有个地方存储着chunksize等,那么要有始有终,骗得chunk的size的p位为1,其下一chunk prevsize p位为0
unlink
1.实施的条件: 关闭了PIE保护、具有堆溢出、知道堆块指针存放在哪里 ,且要避开fastbin,因为fastbin不涉及chunk合并
2.总结:
先创造两个chunk a,b,在chunk a的date区域编写一个新chunk,其结构为payload=p64(随便)+p64(0x30)+p64(fd)+p64(bk)+b’a’*0x10+p64(0x30)+p64(0x90)
若已给出一个数组s记录这两个chunk的date指针,则fd指的是s-0x8,bk指的是s,此时free(chunk b)可以将数组s中记录chunk a的指针改为s-0x8,此时再修改chunk a,修改的就是s-0x8开始的数据区域
得到能够编辑存储chunk指针的位置,如
图中0x602140开始时存储指向chunk date区域的地址,通过某种方式如unlink使得编辑该段,将firstchunk改为free地址,secondchunk改为puts地址,thirdchunk改为atoi地址,此时修改chunk1改的就是free的内容(均为got标位置),若将其改为puts_plt,此时free效果就变为puts,free(secondchunk)变为puts(secondchunk)也就是puts(puts_got),此时就可以将atoi改为system,因为atoi为发送字符即可触发,此时发送/bin/sh地址即可
use after free
范围:内存块被释放后,该指针没有被置为NULL
遇到的情况:比如说add步骤有两个,一个是指向printf的地址和堆content的内容,一个就是其本身,还存在一个system函数,那么将printf的位置改为system,这样当调用printf时就会变成调用system,那要怎么改呢,只要将两个chunk的大小设置不一样,这样当delete后,会直接将a,b的first_chunk归到一起,此时再申请一个大小与first_chunk一样的chunk,就会将其中一个first_chunk变为新申请chunk的second_chunk,这样修改其内容为system即可达到效果
fastbin attack
当其next chunksize要小于其最大fast bin size,先进后出
大小:0x20~0x80
两个前提:1.能够控制chunk的内容
2.漏洞发生在fastbin chunk中
单链链接,所以没有bk,先进后出
fastbin double free:链表只检查fd,不检查bk
先申请两个chunk a,b,然后释放使其进入fastbin,然后再次释放a,这样a的fd就指向b,链表结构:a->b->a,再次申请,将a的fd改为bss_addr,这样,在申请一个chunk就会把b调出来,再次调用,a出,而a的fd指向bss,这样再次申请就会从bss中调出
house of spirit
释放指定地址的chunk
五要求:ismmap位为0,地址对齐(如pre_size所在位置必须是0xXXXX0或0xXXXX4),满足进入fastbin要求,链表头部不能是fake chunk,即不能达成double free
核心:程序申请一个chunk会先有一个point,指向其chunk的date区,若是可以修改其指向其他区,这样free(point)回达到将fakechunk混入fastbin链表中;也可将一个存储各个chunk的date指针的chunkfree掉,再次申请就可以编辑这个chunk
实例:将point指向got地址,泄露出来就可以得到libc_base从而得到system和bin_sh地址
alloc to stack
chunk在被free后依然可以修改其fd,使其申请的第二个chunk为指向的栈
arbitrary alloc
与alloc to stack不同在于一个分配到栈中,一个分配到其它符合size的区域,如 _malloc_hook 的位置
字节错位
与arbitrary alloc相关,因为其所指向_malloc_hook的size不符,所以通过查找 _malloc_hook前面的地址来错位
常规查找:
x/20gx _malloc_hook-0x48
1 | 0x7ffff7dd1a88 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 |
观察到在0x7ffff7dd1b08找到0x7f,在fastbin中属于0x70块,所以我们要malloc(0x60),这样可以获得一个0x70的chunk,地址为0x7ffff7dd1b06,此时将钩子挂在_malloc_hook上,在申请任意地址的chunk就触发了
快速查找:main_arena与malloc相差0x10
1 | p &main_arena |
挂钩子:找出钩子: one_gadget ./libc.so.6
得到的钩子地址要加上libc_base,将其写入_malloc_hook中,此时malloc即可触发
Unsortedbin attack
使用情况:1.先进先出(FIFO)
2.当malloc时,从fastbin,smallbin 找不到对应size,会从unsortedbin中找,若没找到,就会将 unsortedbin中chunk返还到各自bin中(若unsortedbin中chunksize大于申请的,会将其分割,分割剩余部分放回unsortedbin中,其余chunk各回各家)
怎么leak:泄露出main_arena的地址
**合并:**先申请chunk a(0x18),b(0x10),c(0x60)利用堆溢出将b的size改为0x91,然后释放掉b,这样b被释放的大小就变成了0x90,然后申请0x10,这样就会将原先的0x20分割出来,c原先的fd位就指向了main_arena+88,因为我们并未释放c,所以show(c)就ok了
**分割:**申请a(0x18),b(0x20),c(0x70),然后修改b为0x91,再在c的区域设置一个fake_chunk使其prev-size为0x90,size为0x41,然后释放掉chunkb,再申请0x80,这样就能将b的size真的变成0x91,然后释放c,show(b),
怎么找libc_base:
文件放到 IDA 中,找到
malloc_trim函数,就可以获得偏移了。main_arena和__malloc_hook的地址差是 0x10 ,即1
2main_arena_offset=ELF("libc.so.6").symbols["__malloc_hook"] + 0x10
得到其在libc的位置,相减即可得到libc_base
**什么时候用:**将一个全局变量调整为一个较大的值
**corrupted:**当被释放chunk的bk指针指向另一个地址时,此时malloc该chunk后,指向的另一个地址会指向main_arena
页对齐获取libc_base
1 | malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF) |
main_arena_88为unsortedbin泄露出的地址,malloc_hook_s为其在libc的地址
举例:
比如要令magic>某一值,而main_arena大于该值,此时只需让释放的chunk的bk指向&magic-0x10,此时unsortedbin发生corrupted,由先进先出原则,再次申请就变成&magic-0x10指向的chunk的fd指向main_arena,而其fd地址即为&magic
改前:corrupted启动
改后:
largebin attack
largebin共有63个bin,分成六组,每组size公差一致,大于512字节的为large bin
与其他bin区别
fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
!!
一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列 。
多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0 。
size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk
利用:
P2是一个largebin,将他挂进large中,如何挂进看unsortedbin机制,挂完后将其bk和bk_nextsize设为图中所示结构,此时若再次挂进一个size大于P2size的chunkP3,就会将stack_var2和stack_var1的值指向P3,所以也可以达到修改数值的效果。
tcache基础
glibc 2.26引入的,所以应先检查文件glibc版本:
1 | strings libc.so.6 | grep -i 'version' |
结构: tcache_entry和tcache_perthread_struct ,前者是用来链接空闲的chunk(最多一条链有七个),其next是指向与其大小一致的chunk(与fd有点像);tcache_perthread_struct由counts和 tcache_entry组成,用来管理一个tcache链。
tcache使用
tcache执行流程如下:
第一次malloc时,会申请一块内存存放tcache_perthread_struct,这块内存size一般为0x251
释放chunk时,tcache没填满都优先进入tcachebin
在放入tcache后:先放到对应的tcache中,直到tcache被填满(7个);tcache被填满后,接下来再释放chunk,就会直接放进fastbin或者unsorted bin中;tcache中的chunk不会发生合并,不取消inuse bit(未将P位改为0)
重新申请chunk,并且申请的size符合tcache的范围,则先从tcache中取chunk,直到tcache为空
tcache为空后,从bin中找
tcache为全空时,如果fastbin、small bin、unsorted bin中有size符合的chunk,会先把fastbin、small bin、unsorted bin中的chunk放到tcache中,直到填满,之后再从tcache中取
需要注意的是,在采用tcache的情况下,只要是bin中存在符合size大小的chunk,那么在重启之前都需要经过tcache一手。并且由于tcache为空时先从其他bin中导入到tcache,所以此时chunk在bin中和在tcache中的顺序会反过来
tcache内存申请:
tcache->entries不为空时调用tcache_get()函数获取chunk。
tcache_get()
从tcache->entries[tc_idx]获取一个chunk指针,并且tcache->counts减一,.
内存释放:
通过tcache_put()函数放入,该函数直接将chunk放入其头部(LIFO)
tcache attack
tcache poisoning(单链表)
手段:覆盖tcache的next成员变量,就是将申请的两个chunk a,b释放进tcache中,此时通过堆溢出将b的fd覆写为&target,然后tcachebins结构就由b->a变为b->&target,此时连续申请两次即可。
这时候如果target为malloc_hook-0x10的位置,那么直接挂上钩子就可以了
tcache double free
比fastbin还简单,什么都不检查,直接连续两次free
tcache house of spirit
直接将一个指针变量指向fake_chunk的date区并释放,此时会将其放入tcache中,再次申请,就会将其取出
实例:
直接将存放chunk的指针指向某一不可编辑但很重要的chunk,释放再申请,就可以编辑该chunk、
tcache stashing unlink attack
**机制:**只会检查bk字段
malloc申请会从tcachebins开始,而calloc会跳过tcache,直接从对应bins中拿,而此时若smallbins中有符合大小的chunk(bk :a->b->c->d->…,那会直接从其中拿a,并只检验b是否符合,若符合,就直接全部放入tcache中。此时若tcache中剩余两个空位,那么将b->bk改为&target,那么tcache就变成&target->b->…,此时申请就可以得到&target的chunk,从而得到任意修改值的作用。
tcache的chunk->fd是next字段 chunk->bk是key字段
自glibc 2.29,key字段指向tcache_perthread_struct,检查key字段是否指向本身,这样就防止了double free;但不能避免uaf漏洞和tcache poisoning;
但glibc 2.34 无法通过key字段泄露堆地址
触发shell技巧:
111111111111
得到能够编辑存储chunk指针的位置,如
图中0x602140开始时存储指向chunk date区域的地址,通过某种方式如unlink使得编辑该段,将firstchunk改为free地址,secondchunk改为puts地址,thirdchunk改为atoi地址,此时修改chunk1改的就是free的内容(均为got标位置),若将其改为puts_plt,此时free效果就变为puts,free(secondchunk)变为puts(secondchunk)也就是puts(puts_got),此时就可以将atoi改为system,因为atoi为发送字符即可触发,此时发送/bin/sh地址即可
222222222222
挂钩子:找出钩子: one_gadget ./libc.so.6
得到的钩子地址要加上libc_base,将其写入_malloc_hook中,此时malloc即可触发
也可能需要用到realloc,即在_mlloc_hook-0x8的地址上写入gadget,在__malloc_hook处写上realloc的地址。realloc_addr=libc_base+libc.sym[“realloc”]
33333333333
若got表中有strlen的地址,那么将其所指向内容改为system,那么发送“ ;/bin/sh\x00”就会被strlen()函数计数,strlen(/bin/sh) =system(“/bin/sh”),注意与atoi()函数区别
程序开启pie保护,所以要先通过unsortedbin获得libc_base,通常unsortedbin指向main_arena+0x58,与libc偏移位0x3c4b78
4444444444444
- 未清空指针,首先考虑UAF、double free
- 利用unsorted bin获取libc基址
- 利用double free将chunk里指向name chunk指针修改成指向free_hook
- 修改free_hook为system@got
- 执行free即是执行system
堆额外技术
House of Einherjar
涉及free函数的后向合并:
chunk中size位需为0

并且free后会与top chunk合并
gcc -fno-stack-protector -no-pie 1.c -o sss设为pwn文件

举例
1 | `#include <stdio.h>` |
总结:
先设置chunk1,2,3,然后可通过输入chunk1的内容使得在chunk1的date域设置一个假chunk,后利用chun2(0x18,以8结尾,这样才能off-by-one到chunk3的size位)进行off-by-one,并且在该payload中将其pre-size位设为假chunk的size。p64(address) * 2是为了通过unlink检查。
(1)需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分。
(2)我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址。
(3)我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。
House of Force(top chunk操作)
条件:
1.能够以溢出等形式控制top chunk的size域
2.能够自由分配堆尺寸的大小(很大的size)
成果:
可以使得 top chunk 指向我们期望的任何位置,这就相当于一次任意地址写
利用:
有的题目申请了会输出申请的位置,那么这时候可以获得libc_base,因为申请大大堆会通过mmap,其与libc的偏移固定,因此vmmap看libc位置,相减就得到了固定偏移
例子1
一般是把top chunk改为-1

通过修改top chunk的指针到malloc位置附近,从而使下一次分配chunk时含有malloc地址


0x601010-0x602020=-4112
结果:

例子2

首先,由调试得知 __malloc_hook 的地址位于 0x7ffff7dd1b10 ,采取计算
0x7ffff7dd1b00-0x602020-0x10=140737345551056 经过这次 malloc 之后,我们可以观察到 top chunk 的地址被抬高到了 0x00007ffff7dd1b00

House of Lore(small bin)
small bin机制
- malloc(small chunk)操作:最初先由 unsorted bin 处理,如果 unsorted bin 处理不了,glibc malloc 就以此遍历后续的所有 bins,找出第一个满足要求的 bin,如果所有的 bin 都不能满足的话,就转而使用 top chunk,如果 top chunk 大小不够,那么就扩容 top chunk。当再次调用 malloc (small chunk) 的时候,如果该 chunk size 对应的 small bin 不为空,就从该 small bin 链表中取得 small chunk,否则就需要交给 unsorted bin 及之后的逻辑来处理。
- free (small chunk) 操作:当释放 small chunk 的时候,先检查该 chunk 相邻的 chunk 是否为 free,如果是的话就进行合并操作,将这些 chunks 合并成新的 chunk,然后将他们从 small bin 中移除,最后将新的 chunk 添加到 unsorted bin 中。
small bin为双链表,先进先出,所以通过改其bk使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。
举例:
1 | #include <stdio.h> |
victim 会被放入到 unsort bin 中去,然后下一次分配的大小如果比它大,那么将从 top chunk 上分配相应大小,而该 chunk 会被取下 link 到相应的 bin 中。如果比它小 (相等则直接返回),则从该 chunk 上切除相应大小,并返回相应 chunk,剩下的成为 last reminder chunk , 还是存在 unsorted bin 中。
House of Orange(无free)
特点:
题目中不存在 free 函数或其他释放堆块的函数所以。解决该题是为了通过漏洞利用获得 free 的效果。
方法:
当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
正常检验顺序: fastbin、small bins、unsorted bin、large bins ,top chunk
堆以 brk(有mmap和brk) 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。
top chunk size 的要求
- 伪造的 size 必须要对齐到内存页( 一般内存页的大小是 4kb(0x1000)。那么我们伪造的 size 就必须要对齐到这个尺寸 )
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
之后原有的 top chunk 就会执行_int_free从而顺利进入 unsorted bin 中。
例子


就是top chunk所在位置(0x602020)加上其size的后四位为0x1000(所以fake_size大小 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等)
修改:
vmmap可查看
最后成果:

就可以获得main_arena的地址
House of Rabbit(一般为fast bin)
fastbin为单向链表,后进后出
方法:
利用了在 malloc consolidate 的时候 fastbin 中的堆块进行合并时 size 没有进行检查从而伪造一个假的堆块,为进一步的利用做准备。
前提:
1.可以修改 fastbin 的 fd 指针或 size
2.可以触发 malloc consolidate(merge top 或 malloc big chunk 等等)
malloc consolidate:
仅在分配内存时触发,专门处理 fast bin 中的块,强制合并相邻空闲块,并将结果放入 unsorted bin。
所以例子1中malloc(0x1000);0x1000是为了将unsortedbin中合并的chunk放到small bin中
例子1: modify the size of fastbin chunk
1 | unsigned long* chunk1=malloc(0x40); //0x602000` |
效果:达到overlap
例子2 :modify FD pointer
1 | `unsigned long* chunk1=malloc(0x40); //0x602000 ` |
总结:
修改 fastbin chunk 的 size(如上面的 POC 1 所示) 直接构造 overlap chunk,或者修改 fd(如面的 POC 2 所示),让它指向一个 fake chunk,触发 malloc consolidate 之后让这个 fake chunk 成为一个合法的 chunk。
House of Roman
fastbin attack 和 Unsortbin attack 结合的一个小 trick。 该技术用于 bypass ALSR,利用 12-bit 的爆破来达到获取 shell 的目的。且仅仅只需要一个 UAF 漏洞以及能创建任意大小的 chunk 的情况下就能完成利用。
House of Pig
House of Pig 是一个将 Tcache Stash Unlink+ Attack 和 FSOP 结合的攻击,同时使用到了 Largebin Attack 进行辅助。 且程序中仅有calloc.
条件:
1.存在 UAF
2.能执行 abort 流程或程序显式调用 exit 或程序能通过主函数返回
利用流程
- 进行一个 Tcache Stash Unlink+ 攻击,把地址
__free_hook - 0x10写入 tcache_pthread_struct。由于该攻击要求__free_hook - 0x8处存储一个指向可写内存的指针,所以在此之前需要进行一次 large bin attack。 - 再进行一个 large bin attack,修改
_IO_list_all为一个堆地址,然后在该处伪造_IO_FILE结构体。 - 通过伪造的结构体触发
_IO_str_overflowgetshell。
I/Ofile
1.File结构
file在文件流,分配在堆中, FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。 在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。
在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8 。
file实际结构:

vtable 是 IO_jump_t 类型的指针,IO_jump_t 中保存了一些函数指针
vtable内结构
1 | void * funcs[] = { |
1.fread
从文件流中读数据

真正的功能实现在子函数IO_sgetn 中。 在IO_sgetn 函数中会调用IO_XSGETN,而IO_XSGETN 是IO_FILE_plus.vtable 中的函数指针,在调用这个函数时会首先取出 vtable 中的指针然后再进行调用。 所以 在默认情况下函数指针是指向IO_file_xsgetn 函数的,
2.fwrite
向文件流写入数据

主要是调用_IO_XSPUTN 来实现写入的功能。
在IO_XSPUTN 对应的默认函数IO_new_file_xsputn 中会调用同样位于 vtable 中的_IO_OVERFLOW
IO_OVERFLOW 默认对应的函数是IO_new_file_overflow
3.fopen
用于打开文件 ( _IO_file_fopen )

4.fclose
关闭已打开文件的函数( _IO_file_finish )

5.printf/puts
printf 和 puts 是常用的输出函数,在 printf 的参数是以’\n’结束的纯字符串时,printf 会被优化为 puts 函数并去除换行符。
puts 在源码中实现的函数是IO_puts,这个函数的操作与 fwrite 的流程大致相同,函数内部同样会调用 vtable 中的IO_sputn,结果会执行_IO_new_file_xsputn,最后会调用到系统接口 write 函数。
printf 的调用栈回溯如下,同样是通过**_IO_file_xsputn** 实现

2.伪造vtable劫持程序流程
1.原理:
调用fwrite(或其他)时,会通过xsputn调用,此时将xsputn所指的改为system,再向其文件中写入sh或/bin/sh,此时执行fwrite时就会从xsputn(sh)变成system(sh).
2.方式
两种: 一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针。
3.举例:
xsputn在vtable的第八位
第一种:直接改写
1 | #define system_ptr 0x7ffff7a52390; |
在目前 libc2.23 版本下,位于 libc 数据段的 vtable 是不可以进行写入的
改善:伪造vtable
1 | #define system_ptr 0x7ffff7a52390; |
3.FSOP
劫持IO_list_all 的值来伪造链表和其中的IO_FILE 项。触发方法是调用IO_flush_all_lockp , 对应着会调用IO_FILE_plus.vtable 中的_IO_overflow。
要泄露libc.so的基址: _IO_list_all 是作为全局变量储存在 libc.so 中的,不泄漏 libc 基址就不能改写_IO_list_all。

1._IO_flush_all_lockp
不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
当 libc 执行 abort 流程时
当执行 exit 函数时
当执行流从 main 函数返回时
2.条件:
fp->mode <= 0 && fp->IO_write_ptr > fp->_IO_write_base
3.例子:
1 | #define _IO_list_all 0x7ffff7dd2520 |
我们使用分配内存的前 0x100 个字节作为_IO_FILE,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的_IO_overflow 指针。
之后,覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。
通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush 获取_IO_list_all 的值并取出作为_IO_FILE_plus 调用其中的_IO_overflow
24是_IO_OVERFLOW在vtable的位置
4.glibc 2.24下的IO_File
验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。
1.检查方法:
计算 section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;,紧接着会判断 vtable - __start___libc_IO_vtables 的 offset ,如果这个 offset 大于 section_length , 即大于 __stop___libc_IO_vtables - __start___libc_IO_vtables 那么就会调用 _IO_vtable_check() 这个函数。
2.三大技术:
在_IO_FILE 中_IO_buf_base 表示操作的起始地址,_IO_buf_end 表示结束地址,通过控制这两个数据可以实现控制读写的操作。
3.fileno 与缓冲区的相关利用
例如:scanf调用后会初始化IO_File的stdin结构,但并不会初始化stdout的结构
1 | `#include <stdlib.h>` |
调用前:
1 | p _IO_2_1_stdin_ |
调用后:
stdin 初始化的内存是在堆上分配出来的,在这里堆的基址是 0x405000,因为之前没有堆分配因此缓冲区的地址也是 0x405010 : 
但因为前面有一个tcache,所以起始地址在 0x405260
接下来我们尝试修改_IO_buf_base 来实现任意地址读写,全局缓冲区 buf 的地址是 0x7ffff7bec880。修改_IO_buf_base 和_IO_buf_end 到缓冲区 buf 的地址:

之后 scanf 的读入数据就会写入到 0x7ffff7bec880 的位置,同时也可以看到**_IO_read_ptr、_IO_read_base、_IO_read_end、_IO_buf_base、_IO_buf_end** 值也根据_IO_buf_base 的值而有所修改:
5._IO_str_jumps -> overflow(system在allocate_buffer中)
libc中不仅仅只有IO_file_jumps这么一个vtable,还有一个叫IO_str_jumps的 ,这个 vtable 不在 check 范围之内。所以只要将vtable改为IO_str_jumps就行
0
最后构造结果:
构造过程:
1 | `#include <stdio.h>` |
结果:
然后再调用malloc就会将 mian_arena_88+0x68处的_chain成功衔接到fake_file,此时return就会调用 IO_str_overflow函数
6._IO_str_jumps -> finish(system在free_buffer中)
构造结果:
例子:
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`int winner ( char *ptr);`
`int main()`
`{`
`char *p1, *p2;`
`size_t io_list_all, *top;`
`// unsorted bin attack`
`p1 = malloc(0x400-16);`
`top = (size_t *) ( (char *) p1 + 0x400 - 16);`
`top[1] = 0xc01;`
`p2 = malloc(0x1000);`
`io_list_all = top[2] + 0x9a8;`
`top[3] = io_list_all - 0x10;`
``
// _IO_str_overflow conditions
char binsh_in_libc[] = "/bin/sh"; // we can found "/bin/sh" in libc, here i create it in stack
top[0] = 0;
top[4] = 0; // write_base
top[5] = 1; // write_ptr
top[7] = (size_t)&binsh_in_libc; // buf_base
top[8] = 0; // buf_end
// house_of_orange conditions
top[1] = 0x61;
//top[20] = (size_t) &top[18];
top[21] = 2;
top[22] = 3;
top[24] = -1;
top[27] = (size_t)stdin - 0x1160 -8; // _IO_str_jumps地址
top[29] = (size_t) &winner;
/* Finally, trigger the whole chain by calling malloc */
malloc(10);
return 0;
`}`
`int winner(char *ptr)`
`{`
`system(ptr);`
`return 0;`
`}`
结果:
整数溢出
| 类型 | 字节 | 范围 |
|---|---|---|
| short int | 2byte(word) | 0 |
| unsigned short int | 2byte(word) | 0 |
| int | 4byte(dword) | 0 |
| unsigned int | 4byte(dword) | 0 |
| long int | 8byte(qword) | 正: 0 |
| unsigned long int | 8byte(qword) | 0~0xffffffffffffffff |
上界溢出

下界溢出也是一样,只是将add改为sub
情况
1.未限制范围

堆只申请了0x20大小但却能输入很多东西
2.错误的类型转换
范围大的变量赋值给范围小的,如longint->int,从八字节变为四字节,也就是将其截断了,如0x12345678只剩下0x5678
3.只做了单边限制
正常c语言定义一个整形是有符号的,但是当调用read函数时,其第三个参数为size_t,无符号的,所以会造成-1变为0xffff
题目:buuctf上第十九题,先定义一个数小于10,因此scanf输入-1<10,但是当调用read就会加大输入长度,从而使用栈溢出