时过境迁,当前的DMA变成什么样了?DMA对抗浅析"

admin 2026-10-16 05:23:55 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

时过境迁,当前的DMA变成什么样了?DMA对抗浅析

n013ody
n013ody

看雪学苑

2026年10月1日 17:59
上海

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

经历了多年的DMA热潮终于落下帷幕,如今DMA作弊已经形成了相当大的产业链,时过境迁,曾经的最强现在到底是不是入国军呢?正好最近真正在做一些相关探索。

一、出世

本来大家都对抗的好好的你突然用硬件酱味打击这不胡闹吗?galgame里不是这样的,你应该先和我在R3上一决高下最后再在R0打的难舍难分... 相信大家对DMA的工作原理早就有过充分了解所以也不再赘述,非要说就是:

配置空间(Configuration Space)
  CPU 发起 → Root Complex → PCIe TLP(Config) → 设备
  用途:枚举设备、分配 BAR、读写能力寄存器
  特点:请求者是 CPU,不经过 IOMMU
内存访问(DMA)
  设备发起 → PCIe TLP(MemRd/MemWr) → [IOMMU] → 内存控制器 → 物理内存
  用途:设备读写系统内存
  特点:请求者是设备,IOMMU 启用时经其翻译 (后面要考)

是的就这样,使用DMA可以很轻松的做到任意物理内存读取,写入,更重要的是它不会留下痕迹。和很多的技术手段一样,本来DMA是老老实实作研究用的,可是君子无罪怀璧其罪。

总而言之,DMA刚出来的时候确实是完完全全的碾压了市面上所有的反作弊,没有人使用DMA去对抗反作弊,反作弊也就没有对应的需求,自然而然更加的海阔天空,第一批DMA使用者也就切切实实享受到了新世界。

2016年左右,PCILeech发布 (DMA读写从未如此简单):) DMA读写从此工程化

fpga固件        收发 PCIe TLP
  |
leechcore       传输层
  |
vmm/MemProcs    物理内存 → 进程视图、符号解析
  |
上位机程序      读目标数据

19-21年这个时间段就依旧开始出现咋混共作弊的DMA作弊硬件流入市场,配套服务也非常好,固件配套服务也开始出现,逐渐成熟化了,对抗也进入了下一阶段。

二、大众

众所周知,追求小众是一件非常大众的事情,而在反作弊对抗这块可以说大众了,就再也没有翻身的余地了,技术的泛滥与不断迭代,让旧技术狠狠的被碾压过去,不幸的,很显然DMA也没有逃过时代车轮的碾压,就在某个时间节点,DMA的用户迎来了大爆发,有人赚的盆满钵满,固件,教学等等雨后春笋一般冒出,设备成本的降低和名色大噪又反向助推用户数量的增长,于是肉眼可见的,DMA被盯上了。

另一边,反作弊厂商可以说是亚历山大了,于是正式打响对抗战争。

反作弊厂商首先想到的对抗方法是什么呢?既然DMA是设备那显然他们可以先遍历检查一下:

以下代码为伪代码还原示例

ANSI_STRING a; UNICODE_STRING u;
char path[] = \"\\\\Driver\\\\pci\";
RtlInitAnsiString(&a, path);
RtlAnsiStringToUnicodeString(&u, &a, TRUE);
g_pci = ObReferenceObjectByName(&u, ...);

 //遍历 PCI 总线驱动自己的设备链表
 //g_pci + 0x200 是 LIST_ENTRY 头(node+0 = Flink,node+8 = Blink)
 //记录基址 elem = node - 0x280
 //配置空间头就嵌在 elem + 0x04

for (node = *(PVOID *)(g_pci + 0x200); ; node = *(PVOID *)node) {

    Word vendor = *(Word *)(elem + 0x04);
    Word device = *(Word *)(elem + 0x06);

    if (vendor != 0x8086) continue;         // 判断是不是Intel
    if (device <= 0x7D0B) {
        if (device == 0x7D0B || device == 0x201D || device == 0x28C0 ||
            device == 0x467F || device == 0x4C3D) goto matched;
    } else {
        if (device == 0x9A0B || device == 0xA77F || device == 0xAD0B) goto matched;
    }
    continue;

    if (!(flags & 8)) continue;

    先拿这块设备内核 PDO,再找到分配的 MMIO 资源
    PDEVICE_OBJECT pdo = *(PDEVICE_OBJECT *)(elem + 0x108);
    ULONG len = 0;
    status = IoGetDeviceProperty(pdo,
                 0x15 /* DevicePropertyAllocatedResources */,
                 0, NULL, &len);
    if (status != 0xC0000023 /* STATUS_BUFFER_TOO_SMALL */) bail;

    PVOID buf = ExAllocatePoolWithTag(NonPagedPool, 0x200, 'pciR');
    IoGetDeviceProperty(pdo, 0x15, 0x200, buf, &len);

    PCM_RESOURCE_LIST rl = buf;
    if (rl->List[0].PartialResourceList.PartialDescriptors[0].Type
            != 3 /* CmResourceTypeMemory */) bail;
    bar    = ...[0].u.Memory.Start;
    barLen = ...[0].u.Memory.Length;

    //越过 VMD 桥本身,直读它下游总线的配置空间窗口
     if (barLen >= 0x100000) {
        p = MmMapIoSpace(bar + 0x100000, 4, MmNonCached);
        if (*(ULONG *)p == 0xFFFFFFFF) {
            //总线 1 上没东西
            MmUnmapIoSpace(p, 4);
            ExFreePoolWithTag(buf, 0);
        } else {
            //上有设备 → 把它的完整 256 字节配置空间读下来
            MmUnmapIoSpace(p, 4);
            if (barLen >= 0x100000 + 0x1100) {
                out = 0;
                sub_14001BAF0(bar + 0x100000, &out, elem);
            }
        }
        if (barLen >= 0x200000) {
            q = MmMapIoSpace(bar + 0x200000, 4, MmNonCached);
            if (*(ULONG *)q == 0xFFFFFFFF) { MmUnmapIoSpace(q, 4); }
            else { //同上 }
        }
        ExFreePoolWithTag(buf, 0);
    }
    sub_14001BAF0(bar + 0x100000, &out, elem);
}

void sub_14001BAF0(PHYSICAL_ADDRESS addr, ULONG *out, UCHAR *elem)
{
    void *p = MmMapIoSpace(addr, 0x1100, MmNonCached);        // 0x1400d7338
    scratch = ExAllocatePoolWithTag(0x200, 0x1100, 'pcfg');   // 0x1400d743d

    i = 0x1000;
    while (((UCHAR *)p)[i] == 0xFF) i++;                      // 全 0xFF 无设备

    sub_140026280((UCHAR *)p + 1, (UCHAR *)p + 0x1001, 0xFF); // 0x1400d768a

    *out = 1;
    for (i = 0; i < 0x100; i++)                               // 展开 256 条 store8
        elem[0x158 + i] = ((UCHAR *)p)[0x1000 + i];           // → elem[0x158..0x257]

    MmUnmapIoSpace(p, 0x1100);
}

那么这几个case 的十六进制常量是什么呢? 通过查看PCI ID Repository我们可以看到:

0x201D 0x467F 0x4C3D 0x9A0B = \"Volume Management Device NVMe RAID Controller\";
0x28C0 = \"Volume Management Device (VMD)\";
0x7D0B = \"Core Ultra 200H/200V Series Processors VMD\";
0xA77F = \"RST Volume Management Device Controller\";
0xAD0B` = \"Core Ultra 200 Series Processors VMD\";

那就会有人问了,反作弊拿这个elem 0x158-257又是干什么呢?难道里面有什么可以判断是不是DMA的信息吗。

于是我们就查一查:

status = sub_14001C9D0(...);                    // 填充记录(含直读配置空间)
if (status < 0) goto done;

node = *(PVOID *)(P + 0x200);
if (node == (PVOID *)(P + 0x200)) goto empty;   // 空链

out = user_buffer;
out[0x00..0x02]        = elem[0x00..0x02];
*(WORD *)(out + 0x03)  = *(WORD *)(elem + 0x04);      // VendorID
*(WORD *)(out + 0x05)  = *(WORD *)(elem + 0x06);      // DeviceID
*(U64  *)(out + 0x07)  = ((U64)*(ULONG*)(elem+0x120) << 32)
                       |  (U64)*(ULONG*)(elem+0x11C);
*(ULONG*)(out + 0x0F)  = *(ULONG *)(elem + 0x130);
*(U64  *)(out + 0x13)  = *(U64   *)(elem + 0x128);
*(ULONG*)(out + 0x33)  = *(ULONG *)(elem + 0x14C);
*(ULONG*)(out + 0x37)  = *(ULONG *)(elem + 0x150);
*(ULONG*)(out + 0x3B)  = *(ULONG *)(elem + 0x154);
*(U64  *)(out + 0x3F)  = *(U64   *)(elem + 0x158);    // ← 直读配置空间七点
/* 一直到 out+0x27E */

*(ULONG *)r14 = 2;
sub_14001BD00(P + 0x210, out + 0x27E, ...);

直接查资料看看他都收集了哪些东西吧,忘了说了这么多还没贴出来资料:

typedef struct _PCI_COMMON_HEADER
0x00   Vendor ID   USHORT VendorID / PCI_VENDOR_ID 0x00
0x02   Device ID   USHORT DeviceID / PCI_DEVICE_ID 0x02
0x04   Command(16 位)   PCI_ENABLE_IO_SPACE 0x0001 / PCI_ENABLE_MEMORY_SPACE 0x0002 / PCI_ENABLE_BUS_MASTER 0x0004
0x06   Status   PCI_STATUS_CAPABILITIES_LIST 0x0010 等
0x08   Revision ID   UCHAR RevisionID
0x09   ProgIf(编程接口)   UCHAR ProgIf / PCI_CLASS_PROG 0x09
0x0A   SubClass   UCHAR SubClass
0x0B   BaseClass   UCHAR BaseClass / PCI_CLASS_DEVICE 0x0a(16 位,含 SubClass)
0x0E   HeaderType   PCI_HEADER_TYPE 0x0e;0=普通设备,1=PCI 桥,2=CardBus
0x10–0x27   BAR0–BAR5(6×32 位)   ULONG BaseAddresses[PCI_TYPE0_ADDRESSES],PCI_TYPE0_ADDRESSES 为 6
0x28   CIS / CardBus 指针   ULONG CIS
0x2C   Subsystem Vendor ID   USHORT SubVendorID
0x2E   Subsystem ID   USHORT SubSystemID
0x30   Expansion ROM 基址   ULONG ROMBaseAddress
0x34   Capabilities Pointer   UCHAR CapabilitiesPtr / PCI_CAPABILITY_LIST 0x34
0x3C   Interrupt Line   UCHAR InterruptLine
0x3D   Interrupt Pin   UCHAR InterruptPin(只读)

0x00   Vendor / Device ID   身份;DMA 卡常报成网卡/声卡
0x04   Command:bit1 Command寄存器
0x08–0x0B   Revision / ProgIF / SubClass / BaseClass   你是什么设备
0x10–0x27   BAR0–BAR5   MMIO
0x2C   Subsystem Vendor / Device
0x34   Capabilities Pointer   能力链头
0x3C/0x3D   Interrupt Line / PIN   中断

什么是MMIO?

MMIO就是 Memory-Mapped I/O,0x10–0x27  那 6 个 32 位 BAR 是设备声明自己需要一段地址空间的寄存器

#define PCI_BASE_ADDRESS_0   0x10
#define PCI_BASE_ADDRESS_5   0x24
#define  PCI_BASE_ADDRESS_SPACE        0x01  /* 0 = 内存空间, 1 = I/O 空间 */
#define  PCI_BASE_ADDRESS_MEM_TYPE_64  0x04  /* 64 位地址 */
#define  PCI_BASE_ADDRESS_MEM_PREFETCH 0x08  /* 可预取 */

系统给这段空间分配物理地址后把基址写回 BAR;之后 CPU 访问这段物理地址,就是访问设备的板载寄存器。

为什么收集MMIO?

设备内部的寄存器里,包含 DMA 引擎的描述符地址、长度、启动位、完成状态。这些寄存器通常映射在 BAR 里,CPU 只能通过这段 MMIO 去操作 DMA 。

为什么收集 Command 寄存器?

#define PCI_ENABLE_IO_SPACE                 0x0001
#define PCI_ENABLE_MEMORY_SPACE             0x0002   // bit1
#define PCI_ENABLE_BUS_MASTER               0x0004   // bit2
#define PCI_ENABLE_SPECIAL_CYCLES           0x0008
#define PCI_ENABLE_WRITE_AND_INVALIDATE     0x0010
#define PCI_ENABLE_VGA_COMPATIBLE_PALETTE   0x0020
#define PCI_ENABLE_PARITY                   0x0040   // (ro+)
#define PCI_ENABLE_WAIT_CYCLE               0x0080   // (ro+)
#define PCI_ENABLE_SERR                     0x0100   // (ro+)
#define PCI_ENABLE_FAST_BACK_TO_BACK        0x0200   // (ro)
#define PCI_DISABLE_LEVEL_INTERRUPT         0x0400

当 PCI\_ENABLE\_BUS\_MASTER = 1 时,该 Function 可以主动向总线发起 Memory Read / Write Request(即 DMA 操作)。

当 PCI\_ENABLE\_BUS\_MASTER = 0 时,该 Function 不允许发起任何 DMA 请求,只能被动响应 CPU 的访问。

还可以在用户态继续一下

HDEVINFO h = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
for (DWORD i = 0; SetupDiEnumDeviceInfo(h, i, &di); i++) {
    SetupDiGetDeviceRegistryProperty(h, &di, SPDRP_HARDWAREID,
                                     NULL, buf, sizeof buf, NULL);

   //读取Vendor ID对比一下黑名单id
}

这里也有一些被拉黑的数据,收集数据,建立数据库狠狠的拉黑这些板子使用者。

109E   Brooktree Corporation   Bt848 / Bt878 / Bt879 视频采集   pci-ids 109E
1CD7   Nanjing Magewell Electronics(美乐威)   Pro Capture / Eco Capture 采集卡   pci-ids 1CD7
BDBD   Blackmagic Design   DeckLink / Intensity / UltraStudio 采集卡   pci-ids BDBD (注意都是采集卡下面也要考)

校验方案:

//没服务
for (int i = 0; i < total; i++, elem += 0x848) {
    if (*(U32 *)(elem + 0x808) == 0)          //设备没有绑定任何驱动服务
        //上报
}
//有服务
for (int i = 0; i < total; i++, elem += 0x848) {
    if (*(U32 *)(elem + 0x808) == 0)  continue;    // 无服务 → 归 dma_nosrv 管
    if (*(U32 *)(elem + 0x812) != 786) continue;   // ← 第二个判据
    if (ven_match(o, hwid(elem), o->vendor_list))
        report_event(7990, 102, 9015);
}
//假服务:
bool vm = ven_match(o, hwid(elem), o->vendor_list);   // 子串查找
bool vs = svc_match(o, svc(elem),  o->service_list);  // 字符串全等
if (vm && vs) report_event(7990, 102, 9019);

bool ven_match(Type134 *o, char *hwid, vector<string> *list) {
    if (list->empty() || !hwid) return false;
    for (each v in *list) {
        string pat = \"VEN_\" + v;
        if (sub_181737594(hwid, pat.c_str()))    // ← 在 hwid 里找这个子串
            return true;
    }
    return false;
}

if (*(U32 *)(elem + 0x00) == 0 &&
    *(U32 *)(elem + 0x04) == 0 &&
    *(U32 *)(elem + 0x08) != 0 &&              // ← 这一项要求\"非零\"
    *(U32 *)(elem + 0x10) == 0 &&
    *(U32 *)(elem + 0x14) != 0 &&
    all_zero(elem + 0x18, 256) &&              // ← 连续 256 字节全零
    total == 1)                                // ← 记录总数恰好为 1

这里给出参考表也不再赘述了,核心对抗就是你伪造的我就校验,这也是为什么某些垃圾固件确实会拉闸。

elem+0x808   U32   nosrv / wrongsrv   == 0   无驱动服务
elem+0x812   U32   wrongsrv   == 786   服务起不来
硬件 ID 字符串   string   fakesrv   VEN_<厂商> 子串   厂商身份
服务名字符串   string   fakesrv   全等 3 个虚拟机名   伪造服务名
elem+0x00/04/08/10/14   5×U32   hide   零/非零交替   空壳形态
elem+0x18..0x117   256 B   hide   全零   该填未填
记录总数   int   hide   ==1 / 变化   藏得太干净
两来源 ID   U32×2   cfg   互相相等   读取链被改

三、现在

难道说DMA不小众了我就不玩了吗,但那是弱者的思维。哪怕是在VTD大清洗后,还是有相当数量的八旗子弟仍旧抱着DMA四死死不放手,这也能理解,毕竟双机硬件搭配依旧能解决很多问题,比如签名,软件特征等等。

DMA为了应对反作弊厂商的严肃进攻,也是做出了努力,比如他们尝试伪装固件,把收集到的公开的固件刷到自己这里,或者伪装自己的ID用于对抗反作弊的黑固件数据库,甚至是自己实现一个协议去对抗反作弊的行为检测。

不过反作弊也迎来了史上最强VTD vs 现代最强DMA

0xe87f  test al, al
0xe881  je   0x14000e890
0xe883  cmp  byte ptr [0x14003027A], 0        ; g_securePciEnabled
0xe88a  jne  0x14000e912                      ; Secure PCI 已启用 → 跳过整段
0xe890  cmp  dword ptr [rbp - 0x7c], 0x10ec   ; 未启用 → 检查 vendor == Realtek
0xe897  jne  0x14000e912
0xe8a5  lea  rdx, [r13 + 0x10]                ; 遍历设备条目,步长 0x14
0xe8c5  cmp  byte ptr [rcx], 3                ; 子类型 == 3
/* g_securePciEnabled 由 VslGetSecurePciEnabled() 写入(0x14000D586 → 0x140016F5C) */
if (secure_pci_enabled)
    return;                                   //直接过
if (vendor != 0x10EC)
    return;

/* 遍历设备条目,按子类型继续查 */

系统已经启用了 Secure PCI就不管,至于为什么只检查Realtak就懂的都懂了。

不过反作弊也利用起了大杀器,VTD,还记得前面说的IOMMU吗?这里就考到了,DMA要访问是必须经过IOMMU的。

在没有VTD时候,DMA是这样访问的:

设备 ──DMA──▶ 物理地址 0x0000_0000 / 内核代码 / 其他进程 / 其他 VM

而在打开了VTD之后就是如此访问的:

PCIe 设备
   │ DMA 请求(带 IOVA + Requester ID / BDF)
   ▼
VT-d DMA Remapping Engine(Root Complex 里)
   │ 1. 用 BDF 查 Root Entry → Context Entry
   │ 2. 找到这个设备的 I/O 页表(Domain)
   │ 3. IOVA → HPA 翻译
   │ 4. 检查 R/W/EXEC 权限
   ▼
物理内存

0x00 Signature      \"DMAR\"
0x04 Length
0x08 Revision
0x09 Checksum
0x0A OEMID          6 字节
0x10 OEM Table ID   8 字节
0x18 OEM Revision
0x1C Creator ID     4 字节
0x20 Creator Revision
0x24 HAW
0x25 Flags
0x26 Reserved
0x28 Remapping Structures (DRHD/RMRR/ATSR/...)

PCIE设备只能看到IOVA(I/O VirtualAddress),而不是直接拿到真实的物理地址了,此时此刻真实的物理地址由VTD硬件查表页决定。

而如果他要读取就需要系统在IOMMU页表里建映射表,如果没有映射的话就会计入DMAR fault,看到这里你应该也能感觉到VTD的姜维打击了,还是那句话,如果身份互换,我会让你知道我的痛苦,可以说,打开VTD几乎DMA就没有还手之力了。

call NtQuerySystemInformation        ; class 0x4C = SystemFirmwareTableInformation
                                     ; 返回一串 4 字节 ACPI 表签名
loop:
  cmp  ecx, 0x52414D44               ; 'DMAR'
  je   → TRUE
  cmp  ecx, 0x53525649               ; 'IVRS'
  je   → TRUE
end:
  → FALSE

当然DMA玩家也并非全无办法,比如:想办法解析DMAR表篡改数据,或者伪造设备已经打开了VTD。

比如我可以构造一张假的DMAR表:

定位 RSDP → 找到 XSDT 物理地址
解析 XSDT → 收集所有 ACPI 表项地址
扫描物理内存,找 4KB 对齐且空闲的区域(与现有表至少隔 4KB)
在该区域构造一份合法的 DMAR 表(含 DRHD/RMRR 等子结构)
DRHD 寄存器基址选 >4GB 的空闲 PCIe MMIO 页
把新 DMAR 的物理地址写入 XSDT 条目(替换或追加)
修正校验和,回读验证
观察系统 IOMMU 行为变化

关键代码: 取自kEv1nZ0/VTD-Bypass: 全自动过VTD检测

定位 RSDP → XSDT
# 扫描物理内存低 1MB 找 RSDP
for pa in range(0x000E0000, 0x00100000, 16):
    buf = lc.read(pa, 36)
    if buf[0:8] == b'RSD PTR ':
        rsdp = buf
        break

xsdt_pa = struct.unpack_from('<Q', rsdp, 24)[0]  # XsdtAddress at offset 24

找空闲 4KB 对齐区域

def find_free_4k(start, end, used_list):
    \"\"\"找和 used_list 中每个地址至少隔 4KB 的空洞\"\"\"
    for pa in range(start, end, 0x1000):
        conflict = False
        for used in used_list:
            if abs(pa - used) < 0x1000:
                conflict = True
                break
        if not conflict:
            return pa
    return 0

构造 DMAR 表
def build_dmar(dmar_pa):
    dmar = bytearray(48)  # 表头 48 字节
    struct.pack_into('4s', dmar, 0, b'DMAR')
    struct.pack_into('<I', dmar, 4, 48)
    dmar[8] = 1           # Revision
    # OEMID / OEM Table ID / Creator ID 模仿原固件
    # Checksum 后面算
    return dmar

def calc_checksum(buf):
    return (-sum(buf)) & 0xFF

dmar = build_dmar(dmar_pa)
dmar[9] = calc_checksum(dmar)

DRHD 基址选 >4GB MMIO
def pick_drhd_base():
    \"\"\"选 >4GB 的空闲 PCIe MMIO 页\"\"\"
    for pa in range(0x100000000, 0x10000000000, 0x1000):
        if not is_mmio_mapped(pa) and not is_used(pa):
            return pa
    return 0

写入新 DMAR 并挂进 XSDT
# 写入 DMAR 表体
lc.write(dmar_pa, bytes(dmar))

# 替换 XSDT 中的 DMAR 条目
for i, entry in enumerate(entries):
    if lc.read(entry, 4) == b'DMAR':
        lc.write(xsdt_pa + 36 + i*8, struct.pack('<Q', dmar_pa))
        break

# 修正 XSDT 校验和
xsdt_data = lc.read(xsdt_pa, length)
xsdt_data[9] = calc_checksum(xsdt_data)
lc.write(xsdt_pa, xsdt_data)

最后再回读验证一下
verify = lc.read(dmar_pa, 4)
assert verify == b'DMAR', \"DMAR 写入失败\"

verify_xsdt = lc.read(xsdt_pa + 36 + i*8, 8)
assert struct.unpack('<Q', verify_xsdt)[0] == dmar_pa, \"XSDT 条目更新失败\"

当然,大部分反作弊也会在用API判断完后检查DMAR表,也可以利用固件元数据不一致来判断环境是否可信。

*(U32*)(dmar+0x0B)   0x0B   OEMID(0x0A,6) 之内
*(U16*)(dmar+0x0F)   0x0F   OEMID 末 + OEMTableID 首
*(U32*)(dmar+0x1D)   0x1D   CreatorID(0x1C) 之内
RtlCompareMemory(dmar+0x0B, sdt+0x0B, 6)   0x0B 起 6 B

伪代码:
PVOID dmar = FindAcpiTable('DMAR');
PVOID root = FindAcpiTable('XSDT');

bool vt_d_seen = (dmar != NULL);
bool same_oem  = vt_d_seen && RtlCompareMemory(dmar + 0x0B,
                                   root + 0x0B, 6) == 0;

看雪ID:n013ody

https://bbs.kanxue.com/user-home-1045458.htm

\*本文为看雪论坛优秀文章,由 n013ody 原创,转载请注明来自看雪社区

火热售票中!1.25折门票即将售罄

往期推荐

HTB Nimbus渗透测试靶机 Writeup

当高频观测不再经过异常路径:Shadow Cave 与常驻式插桩架构

D3CTF 2026 d3llvm.apk 反调试定位与加密 SO的Dump

一串反引号,十层突破:n1ctf‑2018‑easy\_harder\_php 完整利用链

实现一个EDR不可见的网络通信(将lwip移植到nt内核中)

球分享

球点赞

球在看

点击阅读原文查看更多


时过境迁,当前的DMA变成什么样了?DMA对抗浅析" 网络安全文章

时过境迁,当前的DMA变成什么样了?DMA对抗浅析"

本文浅析DMA作弊技术现状与反作弊对抗。DMA可任意读写物理内存且不留痕迹,早期碾压反作弊。随着设备成本降低,DMA作弊形成产业链,反作弊厂商通过遍历PCI设备、读取配置空间等方式检测DMA设备。文章展示了反作弊检测的伪代码示例,并分析了PCI配置空间结构,为对抗提供思路。
CAN网络节点原理详解:广播通信、报文过滤与节点上下线机制" 网络安全文章

CAN网络节点原理详解:广播通信、报文过滤与节点上下线机制"

本文详解CAN网络节点原理,涵盖广播通信机制、MailBox报文过滤筛选及节点上下线流程。指出所有节点共享通信速度,通过ID识别源地址,利用过滤器实现选择性接收。新节点加入需验证身份防伪装攻击,可借助TJA1145实施合法性校验。建议结合DBC定义识别节点,并通过网络管理协议监控节点状态。
评论:0   参与:  0