首页
社区
课程
招聘
[求助]如何利用OriginalFirstThunk获得引入表中函数名字?
2006-7-25 14:42 11552

[求助]如何利用OriginalFirstThunk获得引入表中函数名字?

2006-7-25 14:42
11552
已经获得了OriginalFirstThunk, 按照Iczelion的PE教程中的说法:

1.检查 OriginalFirstThunk值。若不为0,顺着 OriginalFirstThunk 里的RVA值转入那个RVA数组。若 OriginalFirstThunk 为0,就改用FirstThunk值。

我象这样;
           DWORD Offset = (DWORD) (pSection->VirtualAddress -
                                  pSection->PointerToRawData);
           ThunkData = (PIMAGE_THUNK_DATA)(pImportDesc->
                         OriginalFirstThunk -Offset +
                        NtHeader->OptionalHeader.ImageBase);

访问PIMAGE_THUNK_DATA结构有错么?

如果没有错:
          ImportBN = (PIMAGE_IMPORT_BY_NAME)(ThunkData[0]-Offset +
                        NtHeader->OptionalHeader.ImageBase);

能得到PIMAGE_IMPORT_BY_NAME结构正确么?

如果没错, ImportBN->Name 访问到的就是函数名了么?

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞7
打赏
分享
最新回复 (16)
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-25 15:16
2
0
我不懂C,但用易语言写过

OriginalFirstThunk/FirstThunk

是指向一个IMAGE_THUNK_DATA数组的RVA

IMAGE_THUNK_DATA是一个有0结束的的DWORD数组,有多少个调用API就有多少个成员

每个成员是指向IMAGE_IMPORT_BY_NAME的RVA,检查这个RVA如果这个RVA的值与IMAGE_ORDINAL_FLAG32位与为1

则函数是通过序数引出的,所以不需要更进一步处理了。直接从提取这个RVA的低字节获得序数,然后是下一个IMAGE_THUNK_DATA

双字,如果不为1.就是通过函数名称来引入的,IMAGE_IMPORT_BY_NAME有2个成员,

第一个是个序号,WORD,2字节

另一个就是函数名, 大小不固定,文本型  以0结束

通过我的讲解,你明白了吗?

雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Casimodo 1 2006-7-25 16:27
3
0
谢谢楼上大哥帮忙解释.

在结构上我已经清楚了.

但是我把 VC调试中的得到的数据 与 Stud_PE 相比较发现出入太大了.

就连OriginalFirstThunk的植都不一样.  哪位大大能帮忙看看1楼的地址转换哪里错了么?下面是得到 pSection 的相关函数:

PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva)
{
    unsigned i;
   
    pSection1 = SectionHeader;
    for ( i=0; i < NtHeader->FileHeader.NumberOfSections; i++,pSection1++)
    {
            if ( (rva >= pSection1->VirtualAddress) &&
             (rva < (pSection1->VirtualAddress + pSection1->Misc.VirtualSize)))
            return pSection1;
    }
   
    return 0;
}
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-25 16:46
4
0
因为 PE 格式里用的都是 RVA,所以要在文件里定位所需信息的位置就困难了。访问 PE 文件时通常需要使用 Win32 内存映射 API 将文件映

射入内存。在这个内存映射文件里计算某个 RVA 就有点儿复杂。首先要找出此 RVA 所在的 section,这可以靠循环枚举每一个 section 来完

成。每一个 section header 都保存着 section 的 RVA 和 section 的大小。section 在内存中是保证连续的。因此,不管文件是被映射到内

存还是被操作系统的 loader 加载执行,某块数据距离 section 的首地址的偏移都是一样的,因此要找到内存映射文件的地址,只需要将这个

偏移值加到 section 的基地址上即可。 section 的基地址又可以通过 section 的文件偏移得到,而其文件偏移就保存在相应的 section

header 里。过程很简单,不是吗?

ImageRvaToVa()

别担心,这儿还有个简单的办法。微软为了方便我们提供了 IMAGEHLP.DLL。这个由 DLL export 的函数可以计算给定的 RVA 的内存映射文件的地址。

LPVOID ImageRvaToVa(
PIMAGE_NT_HEADERS NtHeaders,
LPVOID Base,
DWORD Rva,
PIMAGE_SECTION_HEADER *LastRvaSection
);

参数 NtHeaders 指向 IMAGE_NT_HEADERS 结构体的指针。这个结构体就代表着 PE header,定义在 WINNT.H 文件里。指向 PE 文件的指针可以用 IMAGEHLP.DLL export 的 ImageNtHeader() 得到。

Base 用 Win32 的内存映射文件 API 将文件映射入内存时 PE 文件在内存中的基地址。

Rva 给定的 relative virtual address。LastRvaSection,前一个 RVA section。这是个可选的参数,可以传个NULL。 要是指定了具体的值,这个值就得指向一个变量,这个变量保存着上一次
做 RVA 到 VA 转换的 section 的 section 指针。这个参数只是用来优化section 的查找,以
免给定的 RVA 与前一次调用该函数得到的 RVA 都在同一个 section 里还要从头查找。函数首
先检查 LastRVASection,如果所给的 RVA 不在 LastRVASection 里再顺序查找每个 section。

返回值
若函数成功执行,返回值就是映射文件中的 virtual address; 否则,返回 NULL。error number 可以用 GetLastError() 函数得到.
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-25 16:59
5
0
其实很简单的,我乱写的,希望能看地明白

while 节
==============

if rva>节地址 and  rva<节地址+节长度

retun (空间地址+PE地址(RVA)+RVA-节地址+文件偏移)

===================
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Casimodo 1 2006-7-25 17:20
6
0
原来还有这样的函数, 真是白费我几天的精力.

再次谢谢大哥.

那么,用ImageRvaToVa得到的VA-ImageBase就是Offset(文件偏移)了么?
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-25 17:42
7
0
最初由 Casimodo 发布
原来还有这样的函数, 真是白费我几天的精力.

再次谢谢大哥.

那么,用ImageRvaToVa得到的VA-ImageBase就是Offset(文件偏移)了么?


这个其实我也没用过,应该是实际地址,可以直接用

其实还有几个更方便:

MapAndLoad()

IMAGEHLP.DLL 还可以替你完成 PE 文件的内存映射。 MapAndLoad() 函数将请求的 PE 文件映射入内存并用该映
射文件的信息填充 LOADED_IMAGE 结构体。

BOOL MapAndLoad(
LPSTR ImageName,
LPSTR DllPath,
PLOADED_IMAGE LoadedImage,
BOOL DotDll,
BOOL ReadOnly
);

参数
ImageName 载入的 PE 文件的文件名。

DllPath 定位文件的路径。若传递 NULL,则搜索 PATH 环境变量中的路径。

LoadedImage 结构体 LOADED_IMAGE 定义在 IMAGEHLP.H file。该结构体有以下成员:

ModuleName 被加载文件的文件名。

hFile 调用 Create文件的到的句柄。

MappedAddress 文件映射到的内存地址。

FileHeader 指向映射文件的 PE header 的指针。

LastRvaSection 函数将此值设为第一个 section (见 ImageRvaToVa)。

NumberOfSections 所加载的 PE 文件中 sections 的数目。

Sections 指向映射文件的第一个 section header。

Characteristics PE 文件的特征(后面详细介绍)。

fSystemImage 指示是否是内核模式驱动/DLL 的标志。

fDOSImage 指示是否是 DOS 可执行文件的标志。

Links 所加载的 images 的列表。

SizeOfImage image 的大小。

加载 PE 文件后函数将这些成员设为正确的值。

DotDll 若需要查找该文件而且没有指定扩展名,则使用 .exe 或 .dll 作扩展名。 若 DotDll 标志设
为TRUE,则使用 .dll 扩展名; 否则用 .exe 扩展名。

ReadOnly 若设为 TRUE,则文件被映射为 read-only。

返回值
若成功,返回 TRUE; 否则返回 FALSE.

UnMapAndLoad()

使用完映射文件后,应该调用 UnMapAndLoad() 函数。 此函数解除 PE 文件的映射并回收由 MapAndLoad() 分配
的资源。

BOOL UnMapAndLoad( PLOADED_IMAGE LoadedImage );

参数
LoadedImage 指向 LOADED_IMAGE 结构体的指针,该指针就是前面调用 MapAndLoad()
函数返回的指针。

返回值
若成功,则返回 TRUE; 否则返回 FALSE.

我是摘自 Undocumented Windows NT 中文版

站上有下载:

===========================

房间的窗向世界敞开,
微软的窗向世界紧闭,
但在窗棂缝隙间,
透出一线光明。

Warning Undocumented items in the DDK header   
files are subject to change or deletion without notice.  
――Windows 2000 DDK Documentation   

看过此书,即成高手!吾译到兴奋之处不禁拍案。吾对此书之仰慕,由来已久,然苦于国内未见引进,网上亦难觅踪迹。见网络上各方高手所著文章皆引用此书,深知此书之于 Windows NT 不亚于葵花宝典之于武功,对有此书之各路高手亦艳羡不已。然上天终不负我,竟在搜寻另一相关文章时偶得此书。真真个踏破铁鞋无觅处,得来全不费工夫!更令吾深感惭愧之是,书中之部分章节吾之前竟搜到过多次,却因肉眼凡胎,不识真经。如今终获至宝,本应压箱而藏,然吾知定有上进人苦觅此书而不得,决意公之于众。吾决心将书中西洋蛮夷之语译为我大汉文字,一则令我中华文字为此书增辉,二则以利国人师夷长技。遂十有二日手不离键盘,目不离屏幕,整日端坐于电脑前,增批删阅,呕心沥血,终成此译本。

书中涉及处皆是系统核心奥妙之所在,常人皆对此望而却步。内存操作、各类钩挂、系统服务、软件中断等等系统黑客必掌握之绝技,此书皆有论述,且其内容之深入、之实际以往经典中也难得一见。然内容虽深数却不难懂,作者论述深入浅出,稍有难点便细致讲解,更有数处令吾甚感罗嗦,其讲解之到位,可见一斑。更可贵之处在于,此书不仅受人以鱼,更受人以渔。特辟一章专讲反向工程之技巧,其意在鼓励人从此可自立门户,自行探索系统之奥秘。师父领进门,修行在个人,此言不差。有宝典在手,日日勤读善思,不愁大功不成。

学生才疏学浅,为方便国人才敢冒天下之大不韪而译出此书,错漏之处在所难免,望各方师友不吝赐教。译本公开之际,吾甚是兴奋,夜不能寐。为斯文,是以为译序。

  董岩

中华人民共和国五十六年
于国立河北大学坤舆园

=============================
雪    币: 139
活跃值: (111)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
bookworm 3 2006-7-26 02:05
8
0
The BEST URL on PE header format, version 8:

Microsoft Portable Executable and Common Object File Format Specification:
http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

A couple other links:

An In-Depth Look into the Win32 Portable Executable File Format
http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx

PE File Structure
http://www.madchat.org/vxdevl/papers/winsys/pefile/pefile.htm
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Casimodo 1 2006-7-26 14:10
9
0
转换问题解决了, 但是......

DWORD Offset = (DWORD) (pSection->VirtualAddress - pSection->PointerToRawData);
   
        wsprintf(szError,TEXT("%s"),pSection->Name);
        MessageBox(hwnd,szError,"ERROR",MB_OK);

        if((ImportDes->OriginalFirstThunk & IMAGE_ORDINAL_FLAG32) == 1){
                MessageBox(hChildHwnd2,"通过序数引出",szError,MB_OK);
        }
        else{
        
                  pSection = GetEnclosingSectionHeader((DWORD)ImportDes->OriginalFirstThunk);
        Offset = (DWORD) (pSection->VirtualAddress - pSection->PointerToRawData);

                wsprintf(szError,TEXT("%s"),pSection->Name);
                MessageBox(hwnd,szError,"ERROR",MB_OK);
   
                 ThunkData = (PIMAGE_THUNK_DATA)((DWORD)ImportDes->OriginalFirstThunk -Offset + NtHeader->OptionalHeader.ImageBase);
               
                pSection = GetEnclosingSectionHeader((DWORD)(ThunkData->u1.AddressOfData));
        Offset = (DWORD) (pSection->VirtualAddress - pSection->PointerToRawData);

                wsprintf(szError,TEXT("%s"),pSection->Name);
                MessageBox(hwnd,szError,"ERROR",MB_OK);
//到这里程序就出现访问异常, 那个pSection = 0, 不在任何一个段里面
   
                ImportBN = (PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData) - Offset + NtHeader->OptionalHeader.ImageBase);
        
                wsprintf(szError,TEXT("%s"),ImportBN->Name);
                MessageBox(hwnd,szError,"ERROR",MB_OK);
            
        }

问题 1 : 为什么我的IMAGE_IMPORT_BY_NAME结构老是取不对?

         因为这个程序是取其他PE文件的引入函数名,RVA我已经转成Offset了.和Stud_PE对比结构总是不对.

问题 2 : 查找函数名是在IMAGE_DATA_DIRECTORY的第二个成员找到IMAGE_IMPORT_DESCRIPTOR然后IMAGE_THUNK_DATA再IMAGE_IMPORT_BY_NAME吧
         怎么我看到网上还有人说到IAT里面去取?
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Casimodo 1 2006-7-27 15:50
10
0
老问题问题已经搞定:

1.  地址计算错误,应该是这样算: RVA - Offset + pMapping;
    其中pMapping是文件加载的地址,
    Offset = (pSection->VirtualAddress - pSection->PointerToRawData);
    pSection为RVA所在节.

2.  就是在IAT中,而不是在Import Symbols里面.

新的问题是, 我想获得Dll名, 于是用 :

   AddText(hEdit,TEXT("++%s\r\n"),(ImportDes->Name -Offset + (PBYTE)pMapping));

   想把DLl名加到Edit框里. 但是,什么也没有出现,跟踪发现importDes->Name=0  !? 这是为什么? 还有什么结构能获得Dll名?
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-27 16:04
11
0
.版本 2

.数据类型 IMAGE_IMPORT_DESCRIPTOR, , 输入结构,SIZE:4*4+4*1=20
    .成员 OriginalFirstThunk, 整数型, , , 也是Characteristics,原始第一个换长,由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,此数组永不改变
    .成员 TimeDateStamp, 整数型, , , 时间日期戳
    .成员 ForwarderChain, 整数型, , , 中转链
  ★  .成员 Name, 整数型, , , DLL名称的RAV,指向0结尾的ASCII码字符串
    .成员 FirstThunk, 整数型, , , 第一换长,由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,其每个IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组是输入地址表的一部分,并且可以改变。

.数据类型 IMAGE_THUNK_DATA32
    .成员 Function, 整数型, , , AddressOfData||ForwarderString||Function||Ordinal,指向IMAGE_IMPORT_BY_NAME的RVA,如果是由FirstThunk引入的,加载器载入后这里是API的地址,要HOO API,就修改这里

.数据类型 IMAGE_IMPORT_BY_NAME, , 输入名字
    .成员 Hint, 短整数型, , , 一个提示
    .成员 Name, 文本型, 传址, , 以0结尾的、输入符号的(API)ASCII码名字

看看这几个你就明白了!

.版本 2
.支持库 eLIB

' ==============分析导入表【Import Table】==============
加入文本 (#换行符 + “分析导入表【Import Table】 Image Thunk raw + ★ rva + Import by Name||Hint”)
.变量循环首 (1, 1000, 1, i)
    临时整数 [1] = 空间地址 + RVAToOffset (IMAGE_NT_HEADERS.OptionalHeader.DataDirectory [2].VirtualAddress + 20 × (i - 1))
    复制输入结构 (IMAGE_IMPORT_DESCRIPTOR, 临时整数 [1], 20)
    加入文本 (“动态链接库 :” + 指针到文本 (空间地址 + RVAToOffset (IMAGE_IMPORT_DESCRIPTOR.Name)))
    临时整数 [2] = IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk
    .如果真 (临时整数 [2] = 0)
        临时整数 [2] = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
    .如果真结束
    临时整数 [2] = RVAToOffset (临时整数 [2]) + 空间地址
    .变量循环首 (1, 1000, 1, ii)
        临时整数 [3] = 取字节集数据 (指针到字节集 (临时整数 [2] + 4 × (ii - 1), 4), #整数型, )
        .如果真 (临时整数 [3] = 0)
            跳出循环 ()
        .如果真结束
        .判断开始 (位与 (临时整数 [3], #IMAGE_ORDINAL_FLAG32) = 0)  ' 函数是通过名称引出的
            临时整数 [4] = 空间地址 + RVAToOffset (临时整数 [3])  ' IMAGE_IMPORT_BY_NAME的地址
            临时文本 [1] = 指针到文本 (临时整数 [4] + 2)  ' API名称
        .默认
            临时文本 [1] = “@” + 到文本 (取低字 (临时整数 [3]))
        .判断结束
        加入文本 (“      地址 :      ” + 取标准八位十六进制文本 (RVAToOffset (IMAGE_IMPORT_DESCRIPTOR.FirstThunk) + 4 × (ii - 1)) + “     ” + 取标准八位十六进制文本 (IMAGE_IMPORT_DESCRIPTOR.FirstThunk + 4 × (ii - 1)) + “==> ” + 临时文本 [1])
    .变量循环尾 ()
    .如果真 (指针到文本 (临时整数 [1] + 20) = “”)
        跳出循环 ()
    .如果真结束

.变量循环尾 ()
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-27 17:24
12
0
总结一下:

导入表的RAV=IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[2].VirtualAddress

当然,这个RAV要经过转换的,下同

导入表是一个IMAGE_IMPORT_DESCRIPTOR的数组,以全0的IMAGE_IMPORT_DESCRIPTOR为结束

我们看看IMAGE_IMPORT_DESCRIPTOR:

.数据类型 IMAGE_IMPORT_DESCRIPTOR, , 输入结构,SIZE:4*4+4*1=20
    .成员 OriginalFirstThunk, 整数型, , , 也是Characteristics,原始第一个换长,由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,此

数组永不改变
    .成员 TimeDateStamp, 整数型, , , 时间日期戳
    .成员 ForwarderChain, 整数型, , , 中转链
    .成员 Name, 整数型, , , 名称,0结尾的ASCII码字符串
    .成员 FirstThunk, 整数型, , , 第一换长,由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,其每个IMAGE_THUNK_DATA(换长数据)

元素都描述一个函数。此数组是输入地址表的一部分,并且可以改变。

它的第3个成员就是DLL名称的RAV,OriginalFirstThunk指向一永不改变的IMAGE_THUNK_DATA(换长数据)
的RVA构成的数组:

.数据类型 IMAGE_THUNK_DATA
    .成员 Function, 整数型, , , AddressOfData||ForwarderString||Function||Ordinal,指向IMAGE_IMPORT_BY_NAME的RVA,也可以是API的

地址

我画张图来表达:( []代表指针)

假设OriginalFirstThunk=2000

      

OriginalFirstThunk=这里放着一个RUA指针(例如:2000)

                          ↓

   2000        API 1  IMAGE_THUNK_DATA.Function  =        RVA指针   

                                                            ↓

                                                     IMAGE_IMPORT_BY_NAM
                                                                         .Hint   1
                                                                         .Name   "CreateFileA",0

                                                                     
               

  2004          API 2  IMAGE_THUNK_DATA.Function   =      RVA指针   

                                                            ↓

                                                    IMAGE_IMPORT_BY_NAM
                                                                        .Hint   2
                                                                        .Name   "CloseHandle",0
                     

  2008          API 3            
                  
                ......

   2000+n*4     API  n  IMAGE_THUNK_DATA.Function   =      0          ;以0结束

                                 

加载器载入前,  FirstThunk 和 OriginalFirstThunk的情况一样,但载入后就成了这样:

FirstThunk=这里放着一个RUA指针(例如:2000)

                       ↓

   2000        API 1  IMAGE_THUNK_DATA.Function  =      7C801A24      CreateFileA的地址

   2004        API 2  IMAGE_THUNK_DATA.Function   =     7C809B77      CloseHandle的地址
   
   2008         API 3            
                  
                ......

   2000+n*4     API  n  IMAGE_THUNK_DATA.Function   =      0          ;以0结束

假如,程序被载入在400000处,那么我们就可以这样调用CreateFileA:

                           call dword ptr ds:[402000]

如果HOOK API,可以修改IMAGE_THUNK_DATA.Function的值

OK,就到这里了
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-27 18:07
13
0
这个是我用易语言写的RVAToOffset子程序

放上来给你参考

==================================
.子程序 RVAToOffset, 整数型
.参数 RAV, 整数型
.局部变量 i, 整数型
.局部变量 临时整数, 整数型

.变量循环首 (1, IMAGE_NT_HEADERS.FileHeader.NumberOfSections, 1, i)
    RtlMoveMemory (IMAGE_SECTION_HEADER, PE地址 + 248 + 40 × (i - 1), 40)
    临时整数 = RAV - IMAGE_SECTION_HEADER.VirtualAddress
    .如果真 (临时整数 ≥ 0 且 RAV < IMAGE_SECTION_HEADER.VirtualAddress + IMAGE_SECTION_HEADER.VirtualSize)
        返回 (IMAGE_SECTION_HEADER.PointerToRawData + 临时整数)
    .如果真结束

.变量循环尾 ()
返回 (0)

==================================
上传的附件:
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Casimodo 1 2006-7-27 18:12
14
0
问题完全解决了. 好高兴,呵呵.

还是要从Import Symbols 获得结构正确.

再次谢谢 大哥.
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 17 2006-7-27 18:22
15
0
最初由 Casimodo 发布
问题完全解决了. 好高兴,呵呵.

还是要从Import Symbols 获得结构正确.

再次谢谢 大哥.


助人为乐,我一向都是这样的

看到你的问题解决了,我也很高兴

雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bikeghost 2006-7-28 20:28
16
0
学习ing
雪    币: 235
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lemony 1 2006-7-31 15:00
17
0
易语言这东西看了让人有点窝火。。。。。。。
游客
登录 | 注册 方可回帖
返回