GCVE-1988-2026-0417

Vulnerability from gna-1988 – Published: 2026-10-02 04:57 – Updated: 2026-10-02 04:57
VLAI
Title
UAF in XMEye Security Camera
Summary
checksec: $ checksec --file Sofia [*] '/redacted/Sofia' Arch: arm-32-little RELRO: No RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8000) Stack: Executable RWX: Has RWX segments this ones interesting. sofia uses an internal flash fs called WFS. per an online search: WFS is a proprietary filesystem developed by Hikvision for its Network Video Recorders (NVRs) and Digital Video Recorders (DVRs). [1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL) How it works: Instead of formatting hard drives or flash storage with general-purpose filesystems (like FAT32 or NTFS), Hikvision devices write video data directly to the storage media using WFS. [1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL) Purpose: This design minimizes fragmentation and maximizes data-writing speeds for 24/7 video streams. Note: Because it is proprietary, standard computers (Windows/Mac/Linux) cannot natively read a WFS-formatted drive. Data recovery professionals use specialized tools like UFS Explorer to reconstruct and extract videos from these drives. [1] (https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL) ok whatever that means. anyway, per some existing research, wfs holds a block buffer cache, which is a pool of 128 buffers that hold ru’d 512-byte flash blocks in ram. each buffer has a refcount that tracks how many threads are using it. then once refcount is 0 obv we free it and it can be recycled. one of the functions that handle this (per the proximity browser) is int __fastcall sub_5B8BE8(int result) { int v1; // r3 if ( result ) { v1 = *(unsigned __int8 *)(result + 18); *(_BYTE *)(result + 18) = v1 - 1; if ( !v1 ) return sub_5A882C("Trying to free free buffer\n"); } return result; } interestingly it does warn if v1 (if the buffer doesnt exist) is free that a double free is happening. it doesnt actually do anything about it however. also since this is multithreaded, there is a clear as day toctou. the same can be seen for looking up an entry and refcount incrementing: int __fastcall sub_5B8C10(int a1, __int64 a2) { int i; // r1 for ( i = dword_AD2DB4[(unsigned __int8)a2]; i; i = *(_DWORD *)(i + 24) ) { if ( *(_DWORD *)i == a1 && *(_QWORD *)(i + 8) == a2 ) { ++*(_BYTE *)(i + 18); return i; } } return i; } again, no locking. this could mean we could get a toctou and consequently a uaf vuln. maybe if we fuck with this carefully and look at the fields we could potentially get arb r/w. after researching online the struct layout looks like this: struct { int device_no; // ??? size 4 uint64_t block_number; uint8_t uptodate; uint8_t dirty; uint8_t refcount; uint8_t paddingidk; uintptr_t hash_prev; uintptr_t hash_next; uintptr_t lru_prev; uintptr_t lru_next; uintptr_t dataptr; } unfortunately i couldnt give a fuck about actualyl exploiting it, but there is a chance someone could just use the ptrs to leak stuff if youre careful enough. this struct is 40 bytes big, and there are allocations of similar size with controllable data. youll have to groom allocations so these two threads get to play with the buffer properly instead of getting some random buffre. to exploit this (by theory): have thread x do a lookup. it finds your target buf and incremenmts refcount and returns a ptr to the buffer. have another thread decrement at the right time to race with thread x. refcount is now 0, even though thread x is holding it. the other thread gets a new buffer, walking along the lru linked list to find a buffer with refcount 0. it finds thread x’s buffer, sets refcount to 1, evicts it and repurposes it for a different block. uaf, nice. we can now write stuff over the stale ref. the buffer eviction logic: int __fastcall sub_5B8C64(int a1, __int64 a2) { int result; // r0 int v6; // r10 int v7; // r3 int v8; // r2 int v9; // r3 int v10; // r2 int v11; // r3 int v12; // r1 while ( 1 ) { result = sub_5B8C10(a1, a2); if ( result ) break; v6 = dword_9D8750; while ( *(_BYTE *)(v6 + 18) ) { v6 = *(_DWORD *)(v6 + 32); if ( v6 == dword_9D8750 ) return 0; } *(_BYTE *)(v6 + 18) = 1; v7 = *(_DWORD *)(v6 + 24); if ( v7 ) *(_DWORD *)(v7 + 20) = *(_DWORD *)(v6 + 20); v8 = *(_DWORD *)(v6 + 20); if ( v8 ) *(_DWORD *)(v8 + 24) = v7; v9 = *(unsigned __int8 *)(v6 + 8); if ( dword_AD2DB4[v9] == v6 ) dword_AD2DB4[v9] = *(_DWORD *)(v6 + 24); if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) ) sub_5A882C("Free block list corrupted"); v10 = *(_DWORD *)(v6 + 28); *(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32); v11 = *(_DWORD *)(v6 + 32); *(_DWORD *)(v11 + 28) = v10; if ( dword_9D8750 == v6 ) dword_9D8750 = v11; if ( *(_BYTE *)(v6 + 17) ) sub_5B8AE8(v6); *(_BYTE *)(v6 + 17) = 0; *(_BYTE *)(v6 + 16) = 0; v12 = dword_AD2DB4[(unsigned __int8)a2]; *(_DWORD *)v6 = a1; *(_QWORD *)(v6 + 8) = a2; while ( 1 ) { if ( !v12 ) { sub_5B8A8C(v6); return v6; } if ( *(_DWORD *)v12 == a1 && *(_QWORD *)(v12 + 8) == a2 ) break; v12 = *(_DWORD *)(v12 + 24); } *(_DWORD *)v6 = 0; *(_QWORD *)(v6 + 8) = 0; *(_BYTE *)(v6 + 18) = 0; sub_5B8A8C(v6); } return result; } so from here, we can see some interesting stuff. nmotably, we can see if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) ) sub_5A882C("Free block list corrupted"); v10 = *(_DWORD *)(v6 + 28); *(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32); v11 = *(_DWORD *)(v6 + 32); *(_DWORD *)(v11 + 28) = v10; if ( dword_9D8750 == v6 ) given the context and the vairables being reused, we can probably guess: if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) ) sub_5A882C("Free block list corrupted"); prev = *(_DWORD *)(v6 + 28); *(_DWORD *)(prev + 32) = *(_DWORD *)(v6 + 32); next = *(_DWORD *)(v6 + 32); *(_DWORD *)(next + 28) = prev; if ( dword_9D8750 == v6 ) so, we read buf->lru_prev, set prev->lru_next to buf->lru_next, read buf->lru_next, then next->lru_prev = buf->lru_prev. if you have skidded before, this is an unsafe unlink, since theres no validation whatsoever on the ptrs. this means if you control buf+28 and buf+32 we can get an arbitrary write. from there, we cna probably chain this to eventually get an arbitrary read, amybe overwrite a got entry, etc etc. _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/
Severity
No CVSS data available.
Assigner
VULNARCHIVE GNA GNA-1988
GNA scorecard E 38/100 over 445 records in the last 180 days details
Impacted products
Vendor Product Version CPE status
unknown UAF in XMEye Affected: unknown
guessed Create a notification for this product.
Credits

{
  "containers": {
    "cna": {
      "affected": [
        {
          "product": "UAF in XMEye",
          "vendor": "unknown",
          "versions": [
            {
              "status": "affected",
              "version": "unknown"
            }
          ]
        }
      ],
      "credits": [
        {
          "lang": "en",
          "type": "finder",
          "value": "evan"
        }
      ],
      "descriptions": [
        {
          "lang": "en",
          "value": "checksec:\n\n$ checksec --file Sofia\n[*] \u0027/redacted/Sofia\u0027\n    Arch:       arm-32-little\n    RELRO:      No RELRO\n    Stack:      Canary found\n    NX:         NX unknown - GNU_STACK missing\n    PIE:        No PIE (0x8000)\n    Stack:      Executable\n    RWX:        Has RWX segments\n\nthis ones interesting. sofia uses an internal flash fs called WFS. per\nan online search:\n\nWFS is a proprietary filesystem developed by Hikvision for its Network\nVideo Recorders (NVRs) and Digital Video Recorders (DVRs).\n [1] \n(https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)\n How it works: Instead of formatting hard drives or flash storage with\ngeneral-purpose filesystems (like FAT32 or NTFS),\n  Hikvision devices write video data directly to the storage media\nusing WFS. [1] \n(https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)\n  Purpose: This design minimizes fragmentation and maximizes\ndata-writing speeds for 24/7 video streams.\n\n  Note: Because it is proprietary, standard computers\n(Windows/Mac/Linux) cannot natively read a WFS-formatted drive. Data\nrecovery professionals use specialized tools like UFS Explorer to\nreconstruct and extract videos from these drives. [1]\n(https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL)\nok whatever that means. anyway, per some existing research, wfs holds\na block buffer cache, which is a pool of 128 buffers that hold ru\u2019d\n512-byte flash blocks in ram. each buffer has a refcount that tracks\nhow many threads are using it. then once refcount is 0 obv we free it\nand it can be recycled.\n\none of the functions that handle this (per the proximity browser) is\n\nint __fastcall sub_5B8BE8(int result)\n{\n  int v1; // r3\n\n  if ( result )\n  {\n    v1 = *(unsigned __int8 *)(result + 18);\n    *(_BYTE *)(result + 18) = v1 - 1;\n    if ( !v1 )\n      return sub_5A882C(\"Trying to free free buffer\\n\");\n  }\n  return result;\n}\ninterestingly it does warn if v1 (if the buffer doesnt exist) is free\nthat a double free is happening. it doesnt actually do anything about\nit however. also since this is multithreaded, there is a clear as day\ntoctou. the same can be seen for looking up an entry and refcount\nincrementing:\n\nint __fastcall sub_5B8C10(int a1, __int64 a2)\n{\n  int i; // r1\n\n  for ( i = dword_AD2DB4[(unsigned __int8)a2]; i; i = *(_DWORD *)(i + 24) )\n  {\n    if ( *(_DWORD *)i == a1 \u0026\u0026 *(_QWORD *)(i + 8) == a2 )\n    {\n      ++*(_BYTE *)(i + 18);\n      return i;\n    }\n  }\n  return i;\n}\nagain, no locking. this could mean we could get a toctou and\nconsequently a uaf vuln. maybe if we fuck with this carefully and look\nat the fields we could potentially get arb r/w.\n\nafter researching online the struct layout looks like this:\n\nstruct {\n    int device_no;\n    // ??? size 4\n    uint64_t block_number;\n    uint8_t uptodate;\n    uint8_t dirty;\n    uint8_t refcount;\n    uint8_t paddingidk;\n    uintptr_t hash_prev;\n    uintptr_t hash_next;\n    uintptr_t lru_prev;\n    uintptr_t lru_next;\n    uintptr_t dataptr;\n}\nunfortunately i couldnt give a fuck about actualyl exploiting it, but\nthere is a chance someone could just use the ptrs to leak stuff if\nyoure careful enough. this struct is 40 bytes big, and there are\nallocations of similar size with controllable data. youll have to\ngroom allocations so these two threads get to play with the buffer\nproperly instead of getting some random buffre.\n\nto exploit this (by theory):\n\nhave thread x do a lookup. it finds your target buf and incremenmts\nrefcount and returns a ptr to the buffer.\nhave another thread decrement at the right time to race with thread x.\nrefcount is now 0, even though thread x is holding it.\nthe other thread gets a new buffer, walking along the lru linked list\nto find a buffer with refcount 0. it finds thread x\u2019s buffer, sets\nrefcount to 1, evicts it and repurposes it for a different block.\nuaf, nice. we can now write stuff over the stale ref.\nthe buffer eviction logic:\n\nint __fastcall sub_5B8C64(int a1, __int64 a2)\n{\n  int result; // r0\n  int v6; // r10\n  int v7; // r3\n  int v8; // r2\n  int v9; // r3\n  int v10; // r2\n  int v11; // r3\n  int v12; // r1\n\n  while ( 1 )\n  {\n    result = sub_5B8C10(a1, a2);\n    if ( result )\n      break;\n    v6 = dword_9D8750;\n    while ( *(_BYTE *)(v6 + 18) )\n    {\n      v6 = *(_DWORD *)(v6 + 32);\n      if ( v6 == dword_9D8750 )\n        return 0;\n    }\n    *(_BYTE *)(v6 + 18) = 1;\n    v7 = *(_DWORD *)(v6 + 24);\n    if ( v7 )\n      *(_DWORD *)(v7 + 20) = *(_DWORD *)(v6 + 20);\n    v8 = *(_DWORD *)(v6 + 20);\n    if ( v8 )\n      *(_DWORD *)(v8 + 24) = v7;\n    v9 = *(unsigned __int8 *)(v6 + 8);\n    if ( dword_AD2DB4[v9] == v6 )\n      dword_AD2DB4[v9] = *(_DWORD *)(v6 + 24);\n    if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )\n      sub_5A882C(\"Free block list corrupted\");\n    v10 = *(_DWORD *)(v6 + 28);\n    *(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32);\n    v11 = *(_DWORD *)(v6 + 32);\n    *(_DWORD *)(v11 + 28) = v10;\n    if ( dword_9D8750 == v6 )\n      dword_9D8750 = v11;\n    if ( *(_BYTE *)(v6 + 17) )\n      sub_5B8AE8(v6);\n    *(_BYTE *)(v6 + 17) = 0;\n    *(_BYTE *)(v6 + 16) = 0;\n    v12 = dword_AD2DB4[(unsigned __int8)a2];\n    *(_DWORD *)v6 = a1;\n    *(_QWORD *)(v6 + 8) = a2;\n    while ( 1 )\n    {\n      if ( !v12 )\n      {\n        sub_5B8A8C(v6);\n        return v6;\n      }\n      if ( *(_DWORD *)v12 == a1 \u0026\u0026 *(_QWORD *)(v12 + 8) == a2 )\n        break;\n      v12 = *(_DWORD *)(v12 + 24);\n    }\n    *(_DWORD *)v6 = 0;\n    *(_QWORD *)(v6 + 8) = 0;\n    *(_BYTE *)(v6 + 18) = 0;\n    sub_5B8A8C(v6);\n  }\n  return result;\n}\nso from here, we can see some interesting stuff. nmotably, we can see\n\n if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )\n      sub_5A882C(\"Free block list corrupted\");\n    v10 = *(_DWORD *)(v6 + 28);\n    *(_DWORD *)(v10 + 32) = *(_DWORD *)(v6 + 32);\n    v11 = *(_DWORD *)(v6 + 32);\n    *(_DWORD *)(v11 + 28) = v10;\n    if ( dword_9D8750 == v6 )\ngiven the context and the vairables being reused, we can probably guess:\n\n if ( !*(_DWORD *)(v6 + 28) || !*(_DWORD *)(v6 + 32) )\n      sub_5A882C(\"Free block list corrupted\");\n    prev = *(_DWORD *)(v6 + 28);\n    *(_DWORD *)(prev + 32) = *(_DWORD *)(v6 + 32);\n    next = *(_DWORD *)(v6 + 32);\n    *(_DWORD *)(next + 28) = prev;\n    if ( dword_9D8750 == v6 )\nso, we read buf-\u003elru_prev, set prev-\u003elru_next to buf-\u003elru_next, read\nbuf-\u003elru_next, then next-\u003elru_prev = buf-\u003elru_prev. if you have\nskidded before, this is an unsafe unlink, since theres no validation\nwhatsoever on the ptrs. this means if you control buf+28 and buf+32 we\ncan get an arbitrary write. from there, we cna probably chain this to\neventually get an arbitrary read, amybe overwrite a got entry, etc\netc.\n_______________________________________________\nSent through the Full Disclosure mailing list\nhttps://nmap.org/mailman/listinfo/fulldisclosure\nWeb Archives \u0026 RSS: https://seclists.org/fulldisclosure/"
        }
      ],
      "providerMetadata": {
        "dateUpdated": "2026-10-02T04:57:33Z",
        "orgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
        "shortName": "VULNARCHIVE"
      },
      "references": [
        {
          "tags": [
            "technical-description"
          ],
          "url": "https://vuln.freearchive.org/archive/full-disclosure/2026/Sep/42"
        },
        {
          "tags": [
            "technical-description"
          ],
          "url": "https://seclists.org/fulldisclosure/2026/Sep/42"
        },
        {
          "url": "https://nmap.org/mailman/listinfo/fulldisclosure"
        },
        {
          "url": "https://seclists.org/fulldisclosure/"
        },
        {
          "url": "https://www.ufsexplorer.com/ufs-explorer-video-recovery/?srsltid=AfmBOooXEaS7QruR5OQybE2P_I37nUcCgNEqXv09ZCzdtqv211_fx8DL"
        }
      ],
      "source": {
        "defect": [
          "https://seclists.org/fulldisclosure/2026/Sep/42"
        ],
        "discovery": "EXTERNAL"
      },
      "title": "UAF in XMEye Security Camera",
      "x_gcve": [
        {
          "recordType": "advisory",
          "relationships": [],
          "vulnId": "GCVE-1988-2026-0417",
          "x_vulnarchive": {
            "archiveUrl": "https://vuln.freearchive.org/archive/full-disclosure/2026/Sep/42",
            "automated": true,
            "contentSha256": "b4a06f6e6b8a0aad588f146b2048eb0567531fd363aee694f5a88ffa1db92e79",
            "evidenceScore": 6,
            "messageId": "",
            "originalUrl": "https://seclists.org/fulldisclosure/2026/Sep/42",
            "policy": "vulnarchive-1",
            "sourceFormat": "text/html",
            "sourcePublishedAt": "2026-09-15T03:44:45Z"
          }
        }
      ]
    }
  },
  "cveMetadata": {
    "assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
    "assignerShortName": "VULNARCHIVE",
    "datePublished": "2026-10-02T04:57:33Z",
    "dateUpdated": "2026-10-02T04:57:33Z",
    "state": "PUBLISHED",
    "vulnId": "GCVE-1988-2026-0417"
  },
  "dataType": "CVE_RECORD",
  "dataVersion": "5.2"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.