鱼C论坛

 找回密码
 立即注册
查看: 4384|回复: 67

[已解决]pe结构里移动导入表问题

[复制链接]
发表于 2018-4-16 15:50:57 | 显示全部楼层 |阅读模式
100鱼币
本帖最后由 谦虚求学 于 2018-4-17 11:32 编辑

   各位 朋友 大神门好, 我有一个导入表的问题想请教下 朋友门 ,这几天 看了 小甲鱼老师的 PE  结构讲解 非常感兴趣,  但是到了导入表这里 出了些 问题 老想不通,在网上查了下资料,导入表的讲解很少,都很保守,最后找了一个滴水的 导入表视频,听得很带劲,再加上小甲鱼老师的PE结构视频 让我更加了解了 PE ,滴水教育视频里 有一个  导入表注入的作业题 ,我很想把它做出来 ,可是我失败了 ,因为我做的 扩大一个 节表 ,移动导出表,移动重定位表 都成功,到了 移动导入表的时候就是不成功,程序无法启动。我用的 是C语言写的代码,因为我是新手学PE,所以希望 有好心的大神 或者 朋友 能用C语言写的 导入表注入(移动导入表)源代码 发我个,我想知道 我错在哪里 ,我一定研究了一个月了 心好累 ,因为像这种 简单地 移动导入表代码 一定对现在的网络软件不起什么伤害了 ,只能仅供学习 了 ,有的朋友 大神门发下代码 ,WIN32 通讯 线程 进程都没学,高级的也不需要,只要能移动导入表代码成功,发我 ,100鱼币  希望大神不要嫌少  ,跪求。。。。。。。。。
最佳答案
2018-4-16 15:50:58
谦虚求学 发表于 2018-6-3 11:23
我是真心的求教 ,  我感觉 移动导入表 到新的增加节了  ,程序不能运行 这和 IAT表有很大关系,看了看  ...

我什么也不想说

main.c
  1. #pragma warning(disable:4996)

  2. #include "PE_Functions.h"
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <windows.h>

  6. #define FREE_SPACE 0x2000


  7. // *************************************************************************************
  8. IMAGE_IMPORT_DESCRIPTOR *g_dest_iid = NULL;
  9. IMAGE_THUNK_DATA *g_dest_itd = NULL;
  10. IMAGE_IMPORT_BY_NAME *g_dest_iibn = NULL;
  11. char *g_dest_string = NULL;

  12. // 使用全局变量 g_dest_string
  13. // 返回复制后的字符串的 RVA
  14. static DWORD MoveString(char *base, char *src)
  15. {
  16.         DWORD ret = OffsetToRva(base, ((char *)g_dest_string - base));
  17.        
  18.         strcpy(g_dest_string, src);
  19.         g_dest_string += strlen(g_dest_string) + 1;                // g_dest_string 指向下一个
  20.         return ret;
  21. }

  22. // 使用全局变量 g_dest_iibn
  23. static DWORD MoveImportByName(char *base, IMAGE_IMPORT_BY_NAME *src)
  24. {
  25.         DWORD ret = OffsetToRva(base, ((char *)g_dest_iibn - base));

  26.         g_dest_iibn->Hint = src->Hint;
  27.         strcpy(g_dest_iibn->Name, src->Name);

  28.         // 要确保 g_dest_iibn 指向下一个,由于结构体对齐,其他环境可能会出问题
  29.         g_dest_iibn = (IMAGE_IMPORT_BY_NAME *)((char *)g_dest_iibn + sizeof(IMAGE_IMPORT_BY_NAME) + strlen(g_dest_iibn->Name) - 1);

  30.         return ret;
  31. }

  32. // 使用全局变量 g_dest_itd
  33. // 返回复制后的 IMAGE_THUNK_DATA 结构的RVA
  34. static DWORD MoveThunkData(char *base, IMAGE_THUNK_DATA *src)
  35. {
  36.         DWORD ret = OffsetToRva(base, ((char *)g_dest_itd - base));

  37.         for(int i = 0; src[i].u1.AddressOfData != 0; ++i)
  38.         {
  39.                 g_dest_itd->u1.AddressOfData = MoveImportByName(base, (IMAGE_IMPORT_BY_NAME *)(RvaToOffset(base, src[i].u1.AddressOfData) + base));
  40.                 ++g_dest_itd;
  41.         }
  42.         memset(g_dest_itd, 0, sizeof(IMAGE_THUNK_DATA));        // 最后一个全为0的结构
  43.         ++g_dest_itd;                // 指向下一个

  44.         return ret;
  45. }

  46. // 使用全局变量 g_dest_iid
  47. static void MoveImportDescriptor(char *base, IMAGE_IMPORT_DESCRIPTOR *src)
  48. {
  49.         for(int i = 0; src[i].Characteristics != 0; ++i)
  50.         {
  51.                 g_dest_iid->OriginalFirstThunk = MoveThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, src[i].OriginalFirstThunk) + base));
  52.                 g_dest_iid->TimeDateStamp = src[i].TimeDateStamp;                // 原样复制
  53.                 g_dest_iid->ForwarderChain = src[i].ForwarderChain;                // 原样复制
  54.                 g_dest_iid->Name = MoveString(base, RvaToOffset(base, src[i].Name) + base);
  55.                 g_dest_iid->FirstThunk = src[i].FirstThunk;                        // FirstThunk 不做改变

  56.                 ++g_dest_iid;
  57.         }
  58.         memset(g_dest_iid, 0, sizeof(IMAGE_IMPORT_DESCRIPTOR));                        // 最后一个全为0的结构
  59.         ++g_dest_iid;                // 指向下一个
  60. }

  61. // ----------------------------------------------------------------
  62. void DebugPrintImportByName(char *base, IMAGE_IMPORT_BY_NAME *iibn)
  63. {
  64.         printf("\t\tiibn->Hint: %x\n", iibn->Hint);
  65.         printf("\t\tiibn->Name: %s\n", iibn->Name);
  66. }

  67. void DebugPrintThunkData(char *base, IMAGE_THUNK_DATA *itd)
  68. {
  69.         for(int i = 0; itd[i].u1.AddressOfData != 0; ++i)
  70.         {
  71.                 printf("\t{\n");
  72.                 printf("\t\titd[i].u1.AddressOfData: %x\n", itd[i].u1.AddressOfData);
  73.                 DebugPrintImportByName(base, (IMAGE_IMPORT_BY_NAME *)(RvaToOffset(base, itd[i].u1.AddressOfData) + base));
  74.                 printf("\t}\n");
  75.         }
  76. }

  77. void Debug(char *base)
  78. {
  79.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  80.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)(base + idh->e_lfanew);
  81.         DWORD import_rva = inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  82.         IMAGE_IMPORT_DESCRIPTOR *iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + RvaToOffset(base, import_rva));

  83.         for(int i = 0; iid[i].Characteristics != 0; ++i)
  84.         {
  85.                 printf("{\n");
  86.                 printf("\tiid[i].OriginalFirstThunk: %x\n", iid[i].OriginalFirstThunk);
  87.                 DebugPrintThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, iid[i].OriginalFirstThunk) + base));
  88.                 printf("}\n");
  89.                 printf("iid[i].TimeDateStamp: %x\n", iid[i].TimeDateStamp);
  90.                 printf("iid[i].ForwarderChain: %x\n", iid[i].ForwarderChain);
  91.                 printf("iid[i].Name: %x\t\t\t%s\n", iid[i].Name, RvaToOffset(base, iid[i].Name) + base);
  92.                 printf("{\n");
  93.                 printf("\tiid[i].FirstThunk: %x\n", iid[i].FirstThunk);
  94.                 DebugPrintThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, iid[i].FirstThunk) + base));
  95.                 printf("}\n");
  96.                 printf("------------------------------------------------\n");
  97.         }
  98. }
  99. // ----------------------------------------------------------------

  100. // 将导入表移动到 ish 指向的区块
  101. void MoveImportTable(char *base, IMAGE_SECTION_HEADER *ish)
  102. {
  103.         // 0x00  - 0x1FF                存放 IMAGE_IMPORT_DESCRIPTOR
  104.         // 0x200 - 0x3FF                存放 IMAGE_THUNK_DATA
  105.         // 0x400 - 0x9FF                存放 IMAGE_IMPORT_BY_NAME
  106.         // 0xa00 - 0xaFF                存放 dll字符串(ASCII 字符串)

  107.         // 初始化全局变量
  108.         char *data = (char *)(base + ish->PointerToRawData);
  109.         g_dest_iid = (IMAGE_IMPORT_DESCRIPTOR *)(data + 0x00);
  110.         g_dest_itd = (IMAGE_THUNK_DATA *)(data + 0x200);
  111.         g_dest_iibn = (IMAGE_IMPORT_BY_NAME *)(data + 0x400);
  112.         g_dest_string = (char *)(data + 0xa00);

  113.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  114.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)(base + idh->e_lfanew);
  115.         DWORD import_rva = inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  116.         IMAGE_IMPORT_DESCRIPTOR *iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + RvaToOffset(base, import_rva));

  117.         MoveImportDescriptor(base, iid);

  118.         // 修正数据目录表
  119.         inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = OffsetToRva(base, ish->PointerToRawData);
  120. }
  121. // *************************************************************************************

  122. int main(void)
  123. {
  124.         //char *filename = "NewHello.exe";
  125.         //CopyFile("Hello.exe", "NewHello.exe", FALSE);
  126.        
  127.         char *filename = "NewMessageBox.exe";
  128.         CopyFile("MessageBox.exe", "NewMessageBox.exe", FALSE);

  129.         HANDLE hFile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  130.         if(hFile == INVALID_HANDLE_VALUE)
  131.         {
  132.                 printf("Could not open: %s", filename);
  133.                 exit(1);
  134.         }
  135.        
  136.         DWORD orig_file_size = GetFileSize(hFile, NULL);
  137.        
  138.         HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, orig_file_size + FREE_SPACE, NULL);
  139.         LPVOID pMem = MapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ | FILE_MAP_COPY, 0, 0, 0);

  140.         MoveImportTable(pMem, (IMAGE_SECTION_HEADER *)(AddSection(pMem, "hello", 0xb00) + (char *)pMem));

  141.         UnmapViewOfFile(pMem);
  142.         CloseHandle(hMap);
  143.         CloseHandle(hFile);
  144.         return 0;
  145. }
复制代码


PE_Functions.c
  1. #pragma warning(disable:4996)

  2. #include "PE_Functions.h"
  3. #include <windows.h>
  4. #include <time.h>

  5. DWORD PE_Align(DWORD tar_num, DWORD align_to)
  6. {
  7.         DWORD n = tar_num / align_to;
  8.         if(n * align_to < tar_num)
  9.                 ++n;

  10.         return n * align_to;
  11. }

  12. DWORD RvaToOffset(LPVOID base, DWORD rva)
  13. {
  14.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  15.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  16.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  17.         for(int i = 0; i < inh->FileHeader.NumberOfSections; ++i)
  18.         {
  19.                 if((ish[i].VirtualAddress <= rva) && (rva < ish[i].VirtualAddress + ish[i].SizeOfRawData))
  20.                 {
  21.                         return rva - ish[i].VirtualAddress + ish[i].PointerToRawData;
  22.                 }
  23.         }
  24.         return -1;
  25. }

  26. DWORD OffsetToRva(LPVOID base, DWORD offset)
  27. {
  28.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  29.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  30.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  31.         for(int i = 0; i < inh->FileHeader.NumberOfSections; ++i)
  32.         {
  33.                 if((ish[i].PointerToRawData <= offset) && (offset < ish[i].PointerToRawData + ish[i].SizeOfRawData))
  34.                 {
  35.                         return offset - ish[i].PointerToRawData + ish[i].VirtualAddress;
  36.                 }
  37.         }
  38.         return -1;
  39. }

  40. // **************************************************************************************
  41. static DWORD GetVirtualAddress(IMAGE_NT_HEADERS32 *inh, IMAGE_SECTION_HEADER *ish)
  42. {
  43.         DWORD address = ish[inh->FileHeader.NumberOfSections - 1].VirtualAddress;
  44.         DWORD size = ish[inh->FileHeader.NumberOfSections - 1].Misc.VirtualSize;
  45.         return address + size;
  46. }

  47. static DWORD GetPointerToRawData(IMAGE_NT_HEADERS32 *inh, IMAGE_SECTION_HEADER *ish)
  48. {
  49.         DWORD address = ish[inh->FileHeader.NumberOfSections - 1].PointerToRawData;
  50.         DWORD size = ish[inh->FileHeader.NumberOfSections - 1].SizeOfRawData;
  51.         return address + size;
  52. }

  53. // 返回值为 新增区块表的偏移
  54. DWORD AddSection(LPVOID base, const char *section_name, DWORD section_size)
  55. {
  56.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  57.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  58.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  59.         DWORD ret = (DWORD)((char *)&ish[inh->FileHeader.NumberOfSections] - (char *)base);

  60.         strncpy(ish[inh->FileHeader.NumberOfSections].Name, section_name, 8);
  61.         ish[inh->FileHeader.NumberOfSections].Misc.VirtualSize = section_size;
  62.         ish[inh->FileHeader.NumberOfSections].VirtualAddress = PE_Align(GetVirtualAddress(inh, ish), inh->OptionalHeader.SectionAlignment);
  63.         ish[inh->FileHeader.NumberOfSections].SizeOfRawData = PE_Align(section_size, inh->OptionalHeader.FileAlignment);
  64.         ish[inh->FileHeader.NumberOfSections].PointerToRawData = PE_Align(GetPointerToRawData(inh, ish), inh->OptionalHeader.FileAlignment);
  65.         ish[inh->FileHeader.NumberOfSections].PointerToRelocations = 0;
  66.         ish[inh->FileHeader.NumberOfSections].PointerToLinenumbers = 0;
  67.         ish[inh->FileHeader.NumberOfSections].NumberOfRelocations = 0;
  68.         ish[inh->FileHeader.NumberOfSections].NumberOfLinenumbers = 0;
  69.         ish[inh->FileHeader.NumberOfSections].Characteristics = 0xE00000E0;        // 可读可写可执行

  70.         // 对数据区填0
  71.         RtlZeroMemory((char *)base + ish[inh->FileHeader.NumberOfSections].PointerToRawData, ish[inh->FileHeader.NumberOfSections].SizeOfRawData);

  72.         inh->FileHeader.TimeDateStamp = (DWORD)time(NULL);                        // 修改时间戳
  73.         inh->OptionalHeader.SizeOfImage =
  74.                 PE_Align(ish[inh->FileHeader.NumberOfSections].VirtualAddress + ish[inh->FileHeader.NumberOfSections].Misc.VirtualSize,
  75.                 inh->OptionalHeader.SectionAlignment);                                // 调整 SizeOfImage
  76.         inh->FileHeader.NumberOfSections += 1;                                        // 区块个数加1

  77.         return ret;
  78. }
  79. // **************************************************************************************
复制代码


PE_Functions.h
  1. #ifndef _PE_FUNCTIONS_H_
  2. #define _PE_FUNCTIONS_H_

  3. #include <windows.h>

  4. DWORD PE_Align(DWORD tar_num, DWORD align_to);
  5. DWORD RvaToOffset(LPVOID base, DWORD rva);
  6. DWORD OffsetToRva(LPVOID base, DWORD offset);

  7. // 返回值为 新增区块表的偏移
  8. DWORD AddSection(LPVOID base, const char *section_name, DWORD section_size);

  9. #endif
复制代码

最佳答案

查看完整内容

我什么也不想说 main.c PE_Functions.c PE_Functions.h
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-16 15:50:58 | 显示全部楼层    本楼为最佳答案   
谦虚求学 发表于 2018-6-3 11:23
我是真心的求教 ,  我感觉 移动导入表 到新的增加节了  ,程序不能运行 这和 IAT表有很大关系,看了看  ...

我什么也不想说

main.c
  1. #pragma warning(disable:4996)

  2. #include "PE_Functions.h"
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <windows.h>

  6. #define FREE_SPACE 0x2000


  7. // *************************************************************************************
  8. IMAGE_IMPORT_DESCRIPTOR *g_dest_iid = NULL;
  9. IMAGE_THUNK_DATA *g_dest_itd = NULL;
  10. IMAGE_IMPORT_BY_NAME *g_dest_iibn = NULL;
  11. char *g_dest_string = NULL;

  12. // 使用全局变量 g_dest_string
  13. // 返回复制后的字符串的 RVA
  14. static DWORD MoveString(char *base, char *src)
  15. {
  16.         DWORD ret = OffsetToRva(base, ((char *)g_dest_string - base));
  17.        
  18.         strcpy(g_dest_string, src);
  19.         g_dest_string += strlen(g_dest_string) + 1;                // g_dest_string 指向下一个
  20.         return ret;
  21. }

  22. // 使用全局变量 g_dest_iibn
  23. static DWORD MoveImportByName(char *base, IMAGE_IMPORT_BY_NAME *src)
  24. {
  25.         DWORD ret = OffsetToRva(base, ((char *)g_dest_iibn - base));

  26.         g_dest_iibn->Hint = src->Hint;
  27.         strcpy(g_dest_iibn->Name, src->Name);

  28.         // 要确保 g_dest_iibn 指向下一个,由于结构体对齐,其他环境可能会出问题
  29.         g_dest_iibn = (IMAGE_IMPORT_BY_NAME *)((char *)g_dest_iibn + sizeof(IMAGE_IMPORT_BY_NAME) + strlen(g_dest_iibn->Name) - 1);

  30.         return ret;
  31. }

  32. // 使用全局变量 g_dest_itd
  33. // 返回复制后的 IMAGE_THUNK_DATA 结构的RVA
  34. static DWORD MoveThunkData(char *base, IMAGE_THUNK_DATA *src)
  35. {
  36.         DWORD ret = OffsetToRva(base, ((char *)g_dest_itd - base));

  37.         for(int i = 0; src[i].u1.AddressOfData != 0; ++i)
  38.         {
  39.                 g_dest_itd->u1.AddressOfData = MoveImportByName(base, (IMAGE_IMPORT_BY_NAME *)(RvaToOffset(base, src[i].u1.AddressOfData) + base));
  40.                 ++g_dest_itd;
  41.         }
  42.         memset(g_dest_itd, 0, sizeof(IMAGE_THUNK_DATA));        // 最后一个全为0的结构
  43.         ++g_dest_itd;                // 指向下一个

  44.         return ret;
  45. }

  46. // 使用全局变量 g_dest_iid
  47. static void MoveImportDescriptor(char *base, IMAGE_IMPORT_DESCRIPTOR *src)
  48. {
  49.         for(int i = 0; src[i].Characteristics != 0; ++i)
  50.         {
  51.                 g_dest_iid->OriginalFirstThunk = MoveThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, src[i].OriginalFirstThunk) + base));
  52.                 g_dest_iid->TimeDateStamp = src[i].TimeDateStamp;                // 原样复制
  53.                 g_dest_iid->ForwarderChain = src[i].ForwarderChain;                // 原样复制
  54.                 g_dest_iid->Name = MoveString(base, RvaToOffset(base, src[i].Name) + base);
  55.                 g_dest_iid->FirstThunk = src[i].FirstThunk;                        // FirstThunk 不做改变

  56.                 ++g_dest_iid;
  57.         }
  58.         memset(g_dest_iid, 0, sizeof(IMAGE_IMPORT_DESCRIPTOR));                        // 最后一个全为0的结构
  59.         ++g_dest_iid;                // 指向下一个
  60. }

  61. // ----------------------------------------------------------------
  62. void DebugPrintImportByName(char *base, IMAGE_IMPORT_BY_NAME *iibn)
  63. {
  64.         printf("\t\tiibn->Hint: %x\n", iibn->Hint);
  65.         printf("\t\tiibn->Name: %s\n", iibn->Name);
  66. }

  67. void DebugPrintThunkData(char *base, IMAGE_THUNK_DATA *itd)
  68. {
  69.         for(int i = 0; itd[i].u1.AddressOfData != 0; ++i)
  70.         {
  71.                 printf("\t{\n");
  72.                 printf("\t\titd[i].u1.AddressOfData: %x\n", itd[i].u1.AddressOfData);
  73.                 DebugPrintImportByName(base, (IMAGE_IMPORT_BY_NAME *)(RvaToOffset(base, itd[i].u1.AddressOfData) + base));
  74.                 printf("\t}\n");
  75.         }
  76. }

  77. void Debug(char *base)
  78. {
  79.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  80.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)(base + idh->e_lfanew);
  81.         DWORD import_rva = inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  82.         IMAGE_IMPORT_DESCRIPTOR *iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + RvaToOffset(base, import_rva));

  83.         for(int i = 0; iid[i].Characteristics != 0; ++i)
  84.         {
  85.                 printf("{\n");
  86.                 printf("\tiid[i].OriginalFirstThunk: %x\n", iid[i].OriginalFirstThunk);
  87.                 DebugPrintThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, iid[i].OriginalFirstThunk) + base));
  88.                 printf("}\n");
  89.                 printf("iid[i].TimeDateStamp: %x\n", iid[i].TimeDateStamp);
  90.                 printf("iid[i].ForwarderChain: %x\n", iid[i].ForwarderChain);
  91.                 printf("iid[i].Name: %x\t\t\t%s\n", iid[i].Name, RvaToOffset(base, iid[i].Name) + base);
  92.                 printf("{\n");
  93.                 printf("\tiid[i].FirstThunk: %x\n", iid[i].FirstThunk);
  94.                 DebugPrintThunkData(base, (IMAGE_THUNK_DATA *)(RvaToOffset(base, iid[i].FirstThunk) + base));
  95.                 printf("}\n");
  96.                 printf("------------------------------------------------\n");
  97.         }
  98. }
  99. // ----------------------------------------------------------------

  100. // 将导入表移动到 ish 指向的区块
  101. void MoveImportTable(char *base, IMAGE_SECTION_HEADER *ish)
  102. {
  103.         // 0x00  - 0x1FF                存放 IMAGE_IMPORT_DESCRIPTOR
  104.         // 0x200 - 0x3FF                存放 IMAGE_THUNK_DATA
  105.         // 0x400 - 0x9FF                存放 IMAGE_IMPORT_BY_NAME
  106.         // 0xa00 - 0xaFF                存放 dll字符串(ASCII 字符串)

  107.         // 初始化全局变量
  108.         char *data = (char *)(base + ish->PointerToRawData);
  109.         g_dest_iid = (IMAGE_IMPORT_DESCRIPTOR *)(data + 0x00);
  110.         g_dest_itd = (IMAGE_THUNK_DATA *)(data + 0x200);
  111.         g_dest_iibn = (IMAGE_IMPORT_BY_NAME *)(data + 0x400);
  112.         g_dest_string = (char *)(data + 0xa00);

  113.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  114.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)(base + idh->e_lfanew);
  115.         DWORD import_rva = inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  116.         IMAGE_IMPORT_DESCRIPTOR *iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + RvaToOffset(base, import_rva));

  117.         MoveImportDescriptor(base, iid);

  118.         // 修正数据目录表
  119.         inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = OffsetToRva(base, ish->PointerToRawData);
  120. }
  121. // *************************************************************************************

  122. int main(void)
  123. {
  124.         //char *filename = "NewHello.exe";
  125.         //CopyFile("Hello.exe", "NewHello.exe", FALSE);
  126.        
  127.         char *filename = "NewMessageBox.exe";
  128.         CopyFile("MessageBox.exe", "NewMessageBox.exe", FALSE);

  129.         HANDLE hFile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  130.         if(hFile == INVALID_HANDLE_VALUE)
  131.         {
  132.                 printf("Could not open: %s", filename);
  133.                 exit(1);
  134.         }
  135.        
  136.         DWORD orig_file_size = GetFileSize(hFile, NULL);
  137.        
  138.         HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, orig_file_size + FREE_SPACE, NULL);
  139.         LPVOID pMem = MapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ | FILE_MAP_COPY, 0, 0, 0);

  140.         MoveImportTable(pMem, (IMAGE_SECTION_HEADER *)(AddSection(pMem, "hello", 0xb00) + (char *)pMem));

  141.         UnmapViewOfFile(pMem);
  142.         CloseHandle(hMap);
  143.         CloseHandle(hFile);
  144.         return 0;
  145. }
复制代码


PE_Functions.c
  1. #pragma warning(disable:4996)

  2. #include "PE_Functions.h"
  3. #include <windows.h>
  4. #include <time.h>

  5. DWORD PE_Align(DWORD tar_num, DWORD align_to)
  6. {
  7.         DWORD n = tar_num / align_to;
  8.         if(n * align_to < tar_num)
  9.                 ++n;

  10.         return n * align_to;
  11. }

  12. DWORD RvaToOffset(LPVOID base, DWORD rva)
  13. {
  14.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  15.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  16.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  17.         for(int i = 0; i < inh->FileHeader.NumberOfSections; ++i)
  18.         {
  19.                 if((ish[i].VirtualAddress <= rva) && (rva < ish[i].VirtualAddress + ish[i].SizeOfRawData))
  20.                 {
  21.                         return rva - ish[i].VirtualAddress + ish[i].PointerToRawData;
  22.                 }
  23.         }
  24.         return -1;
  25. }

  26. DWORD OffsetToRva(LPVOID base, DWORD offset)
  27. {
  28.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  29.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  30.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  31.         for(int i = 0; i < inh->FileHeader.NumberOfSections; ++i)
  32.         {
  33.                 if((ish[i].PointerToRawData <= offset) && (offset < ish[i].PointerToRawData + ish[i].SizeOfRawData))
  34.                 {
  35.                         return offset - ish[i].PointerToRawData + ish[i].VirtualAddress;
  36.                 }
  37.         }
  38.         return -1;
  39. }

  40. // **************************************************************************************
  41. static DWORD GetVirtualAddress(IMAGE_NT_HEADERS32 *inh, IMAGE_SECTION_HEADER *ish)
  42. {
  43.         DWORD address = ish[inh->FileHeader.NumberOfSections - 1].VirtualAddress;
  44.         DWORD size = ish[inh->FileHeader.NumberOfSections - 1].Misc.VirtualSize;
  45.         return address + size;
  46. }

  47. static DWORD GetPointerToRawData(IMAGE_NT_HEADERS32 *inh, IMAGE_SECTION_HEADER *ish)
  48. {
  49.         DWORD address = ish[inh->FileHeader.NumberOfSections - 1].PointerToRawData;
  50.         DWORD size = ish[inh->FileHeader.NumberOfSections - 1].SizeOfRawData;
  51.         return address + size;
  52. }

  53. // 返回值为 新增区块表的偏移
  54. DWORD AddSection(LPVOID base, const char *section_name, DWORD section_size)
  55. {
  56.         IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)base;
  57.         IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((char *)base + idh->e_lfanew);
  58.         IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *)(inh + 1);

  59.         DWORD ret = (DWORD)((char *)&ish[inh->FileHeader.NumberOfSections] - (char *)base);

  60.         strncpy(ish[inh->FileHeader.NumberOfSections].Name, section_name, 8);
  61.         ish[inh->FileHeader.NumberOfSections].Misc.VirtualSize = section_size;
  62.         ish[inh->FileHeader.NumberOfSections].VirtualAddress = PE_Align(GetVirtualAddress(inh, ish), inh->OptionalHeader.SectionAlignment);
  63.         ish[inh->FileHeader.NumberOfSections].SizeOfRawData = PE_Align(section_size, inh->OptionalHeader.FileAlignment);
  64.         ish[inh->FileHeader.NumberOfSections].PointerToRawData = PE_Align(GetPointerToRawData(inh, ish), inh->OptionalHeader.FileAlignment);
  65.         ish[inh->FileHeader.NumberOfSections].PointerToRelocations = 0;
  66.         ish[inh->FileHeader.NumberOfSections].PointerToLinenumbers = 0;
  67.         ish[inh->FileHeader.NumberOfSections].NumberOfRelocations = 0;
  68.         ish[inh->FileHeader.NumberOfSections].NumberOfLinenumbers = 0;
  69.         ish[inh->FileHeader.NumberOfSections].Characteristics = 0xE00000E0;        // 可读可写可执行

  70.         // 对数据区填0
  71.         RtlZeroMemory((char *)base + ish[inh->FileHeader.NumberOfSections].PointerToRawData, ish[inh->FileHeader.NumberOfSections].SizeOfRawData);

  72.         inh->FileHeader.TimeDateStamp = (DWORD)time(NULL);                        // 修改时间戳
  73.         inh->OptionalHeader.SizeOfImage =
  74.                 PE_Align(ish[inh->FileHeader.NumberOfSections].VirtualAddress + ish[inh->FileHeader.NumberOfSections].Misc.VirtualSize,
  75.                 inh->OptionalHeader.SectionAlignment);                                // 调整 SizeOfImage
  76.         inh->FileHeader.NumberOfSections += 1;                                        // 区块个数加1

  77.         return ret;
  78. }
  79. // **************************************************************************************
复制代码


PE_Functions.h
  1. #ifndef _PE_FUNCTIONS_H_
  2. #define _PE_FUNCTIONS_H_

  3. #include <windows.h>

  4. DWORD PE_Align(DWORD tar_num, DWORD align_to);
  5. DWORD RvaToOffset(LPVOID base, DWORD rva);
  6. DWORD OffsetToRva(LPVOID base, DWORD offset);

  7. // 返回值为 新增区块表的偏移
  8. DWORD AddSection(LPVOID base, const char *section_name, DWORD section_size);

  9. #endif
复制代码

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-17 11:33:37 | 显示全部楼层
   没有一个人回答吗   这问题 难吗
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-17 19:00:15 | 显示全部楼层
本帖最后由 world.com 于 2018-4-17 19:01 编辑

不太清除楼主碰到的问题在什么地方。导入表可以通过数据目录表的第二个元素找到,导入表其实就是一个结构体数组。需要添加一个新区段,将导入表结构体拷贝进去
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-17 19:27:40 | 显示全部楼层
world.com 发表于 2018-4-17 19:00
不太清除楼主碰到的问题在什么地方。导入表可以通过数据目录表的第二个元素找到,导入表其实就是一个结构体 ...

朋友感谢你的回答 好开心 , 我遇到的问题是 我把导入表移动了  数据目录里的导入表RVA和大小 都修改了 ,指向我新移动的导入表的位置 可是不能运行了 ,我找不到我错在那  ,用LordePE软件注入 程序能运行,所以 我想找个别人用C语言写的  简单移动导入表源代码 比较下 看我错在那 ,  希望 朋友能帮助下
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-18 11:30:50 | 显示全部楼层
是不是你找的空位远了,好像要放在一个区段里?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-18 13:46:13 | 显示全部楼层
clauslam 发表于 2018-4-18 11:30
是不是你找的空位远了,好像要放在一个区段里?

我是把最后一个 节 扩大 , 然后把旧的导入表移动过去 ,把最后一个节的属性改为 可写 可读 初始化数据,
这是我看LordePE 注入后 节的属性改的 ,因为我扩大一个节的代码是正确的  可运行的 没有错误,
  兄弟有代码  给我发个  好闷
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-18 15:35:11 | 显示全部楼层
嗯!感觉思路没错,能运行的话可以放OD里面调试一下看看哪里出问题了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-18 19:27:35 | 显示全部楼层
world.com 发表于 2018-4-18 15:35
嗯!感觉思路没错,能运行的话可以放OD里面调试一下看看哪里出问题了

我用 OD 不能加载  ,我就是找不到 问题在那  ,电脑报的是 “无法启动0xc0000005”问题  真烦  ,希望 来个 代码  哎  。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-19 14:43:43 | 显示全部楼层
有没有大神出来  指点指点 啊  这100鱼币 甩不出去了 啊   好心人 在哪里啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-21 11:16:25 | 显示全部楼层
真的没有人学 PE结构吗   真的有真么难吗     等待这大神解答  。。。。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-29 18:19:45 From FishC Mobile | 显示全部楼层
我也是遇到了和楼主同样的问题
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-29 20:15:16 | 显示全部楼层
349561280 发表于 2018-4-29 18:19
我也是遇到了和楼主同样的问题

你能不能找到大神解释下 到底哪里出问题了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-30 00:36:03 | 显示全部楼层
我到是很想看看你写的代码错在哪了,发你的代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-30 12:49:22 | 显示全部楼层
人造人 发表于 2018-4-30 00:36
我到是很想看看你写的代码错在哪了,发你的代码

代码量有点大,我试试能不能发上来
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-30 12:53:03 | 显示全部楼层
  1. void ImportTableInjection()
  2. {
  3.     //#pragma comment (lib,"zhuru.dll")
  4.         char* lpszFile="C:\\1.exe";
  5.         char* pszDllPath="zhuru.dll";
  6.         char* pszFunctionName="ExportFunction";
  7.         LPVOID pFileBuffer=NULL;
  8.         LPVOID pNewFileBuffer=NULL;
  9.        
  10.         PIMAGE_NT_HEADERS pNtHeader=NULL;
  11.         PIMAGE_FILE_HEADER pPeHeader=NULL;
  12.         PIMAGE_OPTIONAL_HEADER32 pOptionHeader=NULL;
  13.         PIMAGE_DATA_DIRECTORY pDataDirectory;
  14.         PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
  15.         PIMAGE_SECTION_HEADER pSectionHeader=NULL;
  16.         BOOL isOk;
  17.         int len=ReadFileToBuffer( lpszFile, &pFileBuffer);
  18.        
  19.         if(len)
  20.         {
  21.                 printf("文件读取成功\n",len);
  22.         }
  23.         else
  24.         {
  25.                 printf("失败");
  26.         }
  27.                 //分配一个新空间在原有文件上增加1000字节
  28.         pNewFileBuffer=malloc(len+0x1000);
  29.         if(!pNewFileBuffer)
  30.         {
  31.                 printf("分配失败");
  32.                 free(pFileBuffer);
  33.                 return;
  34.         }
  35.         memset(pNewFileBuffer,0,len+0x1000);
  36.         memcpy(pNewFileBuffer,pFileBuffer,len);
  37.         pNtHeader=(PIMAGE_NT_HEADERS)((DWORD)pNewFileBuffer+PIMAGE_DOS_HEADER(pNewFileBuffer)->e_lfanew);
  38.         pPeHeader=(PIMAGE_FILE_HEADER)((DWORD)pNtHeader+4);
  39.         pOptionHeader=(PIMAGE_OPTIONAL_HEADER32)((DWORD)pPeHeader+IMAGE_SIZEOF_FILE_HEADER);
  40.         pSectionHeader=(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPeHeader->SizeOfOptionalHeader);
  41.         pDataDirectory=(PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;//数组名就是一个地址 所以不需要在取地址
  42.         pImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewFileBuffer+RvaToFoa( pNewFileBuffer,pDataDirectory[1].VirtualAddress));
  43.         //扩大节 增加的首地址
  44.         char* pNewMoveAdd= (char*)(pNewFileBuffer)+len;
  45.         //修复最后一个节表的元素数据

  46.         PIMAGE_SECTION_HEADER pDestSectionHeader=(pSectionHeader+pPeHeader->NumberOfSections)-1;
  47.         pDestSectionHeader->SizeOfRawData+=0x1000;
  48.         pDestSectionHeader->Misc.VirtualSize=pDestSectionHeader->SizeOfRawData;
  49.         //pDestSectionHeader->Characteristics=pSectionHeader->Characteristics;
  50.         int i=0;
  51.         //移动原来的导入表到新的地址
  52.         while(true)
  53.         {
  54.                 if(        pImportDescriptor->Characteristics==0)
  55.                 {
  56.                         break;//判断结构体20个字节为0则结束(Characteristics为0  表示结束)

  57.                 }
  58.                 if(pImportDescriptor->Name!=0&&pImportDescriptor->TimeDateStamp==0)
  59.                 {
  60.                         memcpy(pNewMoveAdd,pImportDescriptor,0x14);
  61.                         pImportDescriptor++;
  62.                         pNewMoveAdd+=0x14;
  63.                         i++;

  64.                 }

  65.         }
  66.         //增加一个新的导入表
  67.         memcpy(pNewMoveAdd,(pNewMoveAdd-0x14),0x14);
  68.         //辟免导入不需要的表
  69.         PIMAGE_THUNK_DATA pImageThunkData=(PIMAGE_THUNK_DATA)(pNewMoveAdd+0x14*2);//pImageThunkData 结构的位置
  70.         //导入DLL名
  71.         PBYTE pszDllNamePosition=(PBYTE)(pImageThunkData+2);//Dll名字的位置
  72.         memcpy(pszDllNamePosition,pszDllPath,strlen(pszDllPath));
  73.         //确定PIMAGE_IMPORT_BY_NAME的位置
  74.         PIMAGE_IMPORT_BY_NAME pImageImportByName=(PIMAGE_IMPORT_BY_NAME)(pszDllNamePosition+strlen(pszDllPath)+1);
  75.         //初始化 PIMAGE_THUNK_DATA里面的元素
  76.         pImageThunkData->u1.Ordinal=FoaToRva(  pNewFileBuffer,(DWORD)pImageImportByName-(DWORD) pNewFileBuffer )-        pOptionHeader->ImageBase;
  77.         //初始化IMAGE_IMPORT_BY_NAME
  78.         pImageImportByName->Hint=0;
  79.         memcpy(pImageImportByName->Name,pszFunctionName,strlen(pszFunctionName));
  80.         //初始化  OriginalFirstThunk
  81.     ((PIMAGE_IMPORT_DESCRIPTOR)pNewMoveAdd)->OriginalFirstThunk=FoaToRva(  pNewFileBuffer,(DWORD)pImageThunkData-(DWORD) pNewFileBuffer )-        pOptionHeader->ImageBase;
  82.         //初始化 FirstThunk
  83.         ((PIMAGE_IMPORT_DESCRIPTOR)pNewMoveAdd)->FirstThunk=FoaToRva(  pNewFileBuffer,(DWORD)pImageThunkData-(DWORD) pNewFileBuffer )-        pOptionHeader->ImageBase;
  84.         // 初始化 Name
  85.         ((PIMAGE_IMPORT_DESCRIPTOR)pNewMoveAdd)->Name=FoaToRva(  pNewFileBuffer,(DWORD)pszDllNamePosition-(DWORD) pNewFileBuffer )-pOptionHeader->ImageBase;
  86.         //找到新增加的导入表地址 ,修复导入表的入口
  87.         pNewMoveAdd= (char*)(pNewFileBuffer)+len;
  88.         pDataDirectory[1].VirtualAddress=FoaToRva(  pNewFileBuffer,(DWORD)pNewMoveAdd-(DWORD) pNewFileBuffer )-pOptionHeader->ImageBase;
  89.         pDataDirectory[1].Size=(i+1)*0x14;
  90.         //修复可选头里的 元素
  91.         pOptionHeader->SizeOfImage+=0x1000;

  92.         isOk= MeMeryToFile(pNewFileBuffer ,  len+0x1000,"C:\\11.exe" );
  93.                 if(isOk)
  94.                 {
  95.                         printf("存盘成功");
  96.                         return ;
  97.                 }
  98.                 free(pFileBuffer);
  99.                 free(pNewFileBuffer);


  100.   

  101. }
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2018-4-30 12:53:16 | 显示全部楼层
谦虚求学 发表于 2018-4-30 12:49
代码量有点大,我试试能不能发上来

zip压缩,然后网盘共享
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-30 12:54:46 | 显示全部楼层
  1. int ReadFileToBuffer(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
  2. {
  3.         FILE *pFile=NULL;
  4.         DWORD  Filesize=0;
  5.         LPVOID pTempFileBuffer=NULL;
  6.        
  7.         pFile=fopen(lpszFile, "rb");
  8.         if(!pFile)
  9.         {
  10.                 printf("无法打开文件");
  11.         return NULL;
  12.         }
  13.         fseek(pFile,0,SEEK_END);
  14.         Filesize=ftell(pFile);
  15.         fseek(pFile,0,SEEK_SET);
  16.         pTempFileBuffer=malloc(Filesize);
  17.         if(!pTempFileBuffer)
  18.         {
  19.                 printf("分配失败");
  20.                 fclose(pFile);
  21.         return NULL;
  22.         }
  23.         size_t  n=fread(pTempFileBuffer,Filesize,1,pFile);
  24.                   if(!n)
  25.                           {
  26.                                   printf("读取数据失败");
  27.                                   free(pTempFileBuffer);
  28.                                   fclose(pFile);
  29.                                   return NULL;
  30.                           }
  31.      *pFileBuffer= pTempFileBuffer;
  32.           pTempFileBuffer =NULL;
  33.           fclose(pFile);
  34.           return Filesize;
  35.                           

  36. }
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-30 12:57:07 | 显示全部楼层
  1. //存盘
  2. BOOL MeMeryToFile(LPVOID pNewBuffer, SIZE_T Size,OUT LPSTR File)
  3. {
  4.         FILE *fp=NULL;
  5.         fp=fopen( File,"wb+");
  6.         if(fp==NULL)
  7.         {
  8.                 return FALSE;
  9.         }
  10.         fwrite(pNewBuffer,Size,1,fp);
  11.         fclose(fp);
  12.         fp=NULL;
  13.         return TRUE;

  14. }
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2018-4-30 13:06:25 | 显示全部楼层
这些代码的意思是把文件打开 在文件里扩大文件,在扩大的最后一个节里移动导入表,在创造一个新的导入表结构等等 ,在保存文件 ,此时感觉是不是少了个文件拉伸环节,但是拉伸了还有压缩文件在保存,心理号挠。求人指点
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-26 12:36

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表