首页
社区
课程
招聘
[旧帖] [求助]获取PspCidTable的地址的一个问题 0.00雪花
发表于: 2009-8-2 20:42 2669

[旧帖] [求助]获取PspCidTable的地址的一个问题 0.00雪花

2009-8-2 20:42
2669
网上获取PspCidTable的地址的源码为:
addr = (PUCHAR) MmGetSystemRoutineAddress(&pslookup);
                for (p=addr;p<addr+PAGE_SIZE;p++)
                {
                        if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))
                        {
                                cid=*(PULONG)(p+2);
                                return cid;
                                break;
                        }
                }
对于其中的条件判断语句*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))以及cid=*(PULONG)(p+2)我一直弄不明白。0x35ff和0xe8是怎么的到的?还有就是for循环终止语句为什么是p<addr+PAGE_SIZE?
   希望各位大侠赐教,不甚感激

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (12)
雪    币: 89
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
好心人帮帮忙呀
2009-8-3 15:25
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
3
该循环通过搜索PsLookupProcessByProcessId函数的代码,找到其中使用PspCidTable的那行语句,从而得到PspCidTable的位置。
想要看明白这段代码,首先要了解PsLookupProcessByProcessId的函数代码内容:

lkd> uf PsLookupProcessByProcessId
nt!PsLookupProcessByProcessId:
805d40ea 8bff            mov     edi,edi
805d40ec 55              push    ebp
805d40ed 8bec            mov     ebp,esp
805d40ef 53              push    ebx
805d40f0 56              push    esi
805d40f1 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d40f7 ff7508          push    dword ptr [ebp+8]
805d40fa 8bf0            mov     esi,eax
805d40fc ff8ed4000000    dec     dword ptr [esi+0D4h]
805d4102 ff35c0495680    push    dword ptr [nt!PspCidTable (805649c0)]
805d4108 e877ad0300      call    nt!ExMapHandleToPointer (8060ee84)

从以上内容可见,只要从PsLookupProcessByProcessId函数的开头开始搜索,找到以下代码
805d4102 ff35c0495680    push    dword ptr [nt!PspCidTable (805649c0)]
就可得到PspCidTable的位置。

具体来说是以这行代码开头的0x35FF和下一句代码开头的0xE8为判断标志,当
((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8)
满足时,说明此时p正是这行代码的开头。
而此时p+2处保存的这个32位DWORD值,正是PspCidTable的位置。
因此有cid=*(PULONG)(p+2);

至于以分页大小PAGE_SIZE为范围,是指定一个搜索范围,以免因某些没有预测到的问题,导致搜索不到相应特征,而出现循环过久乃其访问到不可用内存而导致异常。

以上就是这个循环的原理,即利用导出函数中的特征码搜索来定位未导出的函数或内核结构。
2009-8-3 19:11
0
雪    币: 89
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
多谢楼上的帮助。还有两个不懂的地方想请教一下。一个是805d4102 ff35c0495680    push    dword ptr [nt!PspCidTable (805649c0)]这句。此时(PUSHORT)p应该是ff35呀(我知道cpu存储数据的时候是倒着存的,存储指令的时候也是倒着存的吗?)。还有最后cid应该是32位的。但是用*(PULONG)(p+2)取得的是64位的呀
2009-8-3 22:23
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=benmozhou;665737]多谢楼上的帮助。还有两个不懂的地方想请教一下。一个是805d4102 ff35c0495680    push    dword ptr [nt!PspCidTable (805649c0)]这句。此时(PUSHORT)p应该是ff35呀(我知道cpu存储数据的时候是倒着存的,存储指令的时候也是倒着...[/QUOTE]
CPU存储数据是先存低位字节再存高位字节,而存储指令的时候不是倒着来的。
但是,当你要把这个指令的机器码当成数据的时候:
(PUSHORT)p把p转换成了USHORT的指针,因此*(PUSHORT)p就是把p看成USHORT指针时该处保存的USHORT的值,在内存中是FF 35,相应的USHORT值当然就是0x35FF
(PULONG)(p+2),PULONG就是ULONG型数据的指针,因此*(PULONG)(p+2)就是将(p+2)当成是一个指向ULONG的指针时,该处保存的ULONG值,而ULONG是32位无符号整数(哪有64位……)。
楼主还是得学好C的基础啊……
2009-8-3 22:39
0
雪    币: 89
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
呵呵,谢谢了。。。讲的很详细。顺便问个小问题,遍历PspCidTable获取进程时候,对于pid用了for(i=0x0;i<0x4e1c;i++)的循环。并且当pid属于0到0x800的时候在第一级句柄表中搜索,这个我懂,因为一张表最多容纳0x800个表项。然后在pid大于0x800时候就在第二张表中搜索,这儿我就有点不懂了。既然一张表中只能容纳0x800个表项,那么0x800到0x4e1c的范围是相当大的,第二张表怎么能容纳下呢?
2009-8-3 23:07
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
7
搜了一下相关代码,如http://bbs.pediy.com/showthread.php?t=59680中给出的,发现有些代码还真的是默认最多只有两张1级表,这当然是不对劲的,如果真的要到0x4e1c的话。

实际上gz1X的文章(http://hi.baidu.com/gz1x/blog/item/d99aeefa4d1c92ddb48f31b9.html)中以0x4e1c这个值作为上限是不对的,记得驱网有一帖子里曾经有人试过PID超过这个值的。
所以不偷懒也不会太累赘的作法,我想应该是:
1.使用HANDLE_TABLE结构中的NextHandleNeedingPool,这个值是说明在CID(PID)超过哪个值时就必须再加入一个新表,说明此时有效的CID(PID)值尚未超过这个值,因此使用这个值为上限就可以。
2.或者直接看看二级表中有多少个非0指针,有n个非0指针对应n个1级表,也就对应当前有效CID(PID)值的上限为n*0x800
上面两种方法得到的当前有效PID的上限值应该是一样的,而且只会比真正的当前最大CID(PID)值大而不会小。
2009-8-4 19:32
0
雪    币: 170
活跃值: (90)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
8
//======================================获取pspcidtable地址CODE1=======================
//利用PsLookupXX函数搜索特征码0x35ff和0xe8获取
ULONG GetAddrFromProcessId()
{  
        UNICODE_STRING pslookup;
        PUCHAR addr;              //单字节指针
        PUCHAR p;                 //单字节指针
        ULONG q;                  //四字节
        RtlInitUnicodeString(&pslookup,L"PsLookupProcessByProcessId");
        addr=(PUCHAR)MmGetSystemRoutineAddress(&pslookup);
        KdPrint(("[PsLookupProcessByProcessId] addr:0x%x\n",addr));
        for(p=addr;p<addr+PAGE_SIZE;p++)
        {
                if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))
                {  
                        q=*(PULONG)(p+2);
                        KdPrint(("[GetAddrFromProcessId] pspcidtable:0x%x\n",q));
                        return q;
                        break;
                }
        }
        return 0;
}
//======================================获取pspcidtable地址CODE2=======================

//利用KPCR取得
ULONG GetAddrFromKpcr()
{
        ULONG pspcidtable;
        //#define kpcr   0xffdff000
        //kpcr+0x37 是KdVersionBlock
    //KdVersionBlock+0x80是pspcidtable指针
        pspcidtable=*((PULONG)((*(PULONG)(kpcr+0x34)) + (ULONG)(0x80)));
        KdPrint(("[GetAddrFromKpcr] pspcidtable:0x%x\n",pspcidtable));
        return pspcidtable;
       
}
2009-8-4 23:04
0
雪    币: 170
活跃值: (90)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
9
//======================================获取pspcidtable地址CODE1=======================
//利用PsLookupXX函数搜索特征码0x35ff和0xe8获取
ULONG GetAddrFromProcessId()
{  
UNICODE_STRING pslookup;
PUCHAR addr;              //单字节指针
PUCHAR p;                 //单字节指针
ULONG q;                  //四字节
RtlInitUnicodeString(&pslookup,L"PsLookupProcessByProcessId");
addr=(PUCHAR)MmGetSystemRoutineAddress(&pslookup);
KdPrint(("[PsLookupProcessByProcessId] addr:0x%x\n",addr));
for(p=addr;p<addr+PAGE_SIZE;p++)
{
  if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))
  {  
   q=*(PULONG)(p+2);
   KdPrint(("[GetAddrFromProcessId] pspcidtable:0x%x\n",q));
   return q;
   break;
  }
}
return 0;
}
//======================================获取pspcidtable地址CODE2=======================
//利用KPCR取得
ULONG GetAddrFromKpcr()
{
ULONG pspcidtable;
//#define kpcr   0xffdff000
//kpcr+0x37 是KdVersionBlock
    //KdVersionBlock+0x80是pspcidtable指针
pspcidtable=*((PULONG)((*(PULONG)(kpcr+0x34)) + (ULONG)(0x80)));
KdPrint(("[GetAddrFromKpcr] pspcidtable:0x%x\n",pspcidtable));
return pspcidtable;

}
//====================================获取pspciatable地址CODE3=========================
//KdEnableDebugger->KdInitSystem->KdDebuggerDataBlock->KDDEBUGGER_DATA32->PspCidTable
//======================================================================================
//====================================遍历pspcidtable CODE1=========================
//利用导出的ExEnumHandleTable(ntoskrnl.exe导出函数),可以直接操作,不需要根据GMM自己定位地址
//================================================================================
//====================================自己遍历pspcidtable表=========================
//=============通过当前进程获取进程对象指针(PsGetCurrentProcess函数获取object指针转换成object_header里面记录对象类型)===============
typedef struct _OBJECT_HEADER
{
union
{
  struct
  {
   LONG PointerCount;
   LONG HandleCount;
  };
  LIST_ENTRY Entry;
};
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
  //POBJECT_CREATE_INFORMATION ObjectCreateInfo;
  PVOID QuotaBlockCharged;
};
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
ULONG  GetProcessType()
{
ULONG  type;
ULONG  objecttoeprocess;
objecttoeprocess=(ULONG)PsGetCurrentProcess();
//object转换成object_header完全可以object-0x18
objecttoeprocess=(ULONG)OBJECT_TO_OBJECT_HEADER(objecttoeprocess);
type=*(PULONG)(objecttoeprocess+TYPE);
KdPrint(("[GetProcessType] type:0x%x\n",type));
return type;
}
//===================================采用单链表记录进程Eprocess地址================================================
void RecordProcess(ULONG address) //address  
{  
PROCESSINFO *r;
if(head==NULL)
{
  head=(PROCESSINFO *)ExAllocatePoolWithTag(NonPagedPool,sizeof(PROCESSINFO),MEM_TAG);//分配头指针
  if(head==NULL)
  {
   KdPrint(("[RecoardProcess] Aloocate error\n"));
  }
  //分配内存,用完必须释放,否则内存泄漏
  head->addr=0x0;
}
if(head->addr==0x0)
{
  head->addr=address;
  KdPrint(("[RecordProcess] head->addr:0x%x",head->addr));
  p=head;
}
else
{
  r=(PROCESSINFO *)ExAllocatePoolWithTag(NonPagedPool,sizeof(PROCESSINFO),MEM_TAG);
  if(r==NULL)
  {
   KdPrint(("[RecoardProcess] Aloocate error\n"));
  }
  p->next=r;
  p=r;
  r->addr=address;
  KdPrint(("[RecoardProcess] r->addr:0x%x\n",r->addr));
  r->next=NULL;
}
}
//==================================根据PID找对象指针=====================================================
void GetPointerToObject(ULONG table,ULONG pid)//函数返回的object即指向EPROCESS的指针
{   

    ULONG object,objectheader;
    ULONG NextFreeTableEntry;
    ULONG processtype,type;
    ULONG flags;
    processtype=GetProcessType();//调用函数获取进程类型
    if(MmIsAddressValid((PULONG)(table+pid*2)))
    {  
        if(MmIsAddressValid((PULONG)(table+ pid*2 +NEXTFREETABLEENTRY)))
        {
            
            NextFreeTableEntry=*(PULONG)(table + pid*2 + NEXTFREETABLEENTRY);
            if(NextFreeTableEntry==0)//正常的handle_table_entry中NextFreeTableEntry为0
            {
                object=*(PULONG)(table+pid*2);
                object=((object | 0x80000000)& 0xfffffff8);//转换为对象指针
                KdPrint(("[GetPointerToObject] object:0x%x\n",object));    //函数要记录的进程ERROCESS地址
                objectheader=(ULONG)OBJECT_TO_OBJECT_HEADER(object);//获取对象头指针
                KdPrint(("[GetPointerToObject] objectheader:0x%x\n",objectheader));
                if(MmIsAddressValid((PULONG)(objectheader+TYPE)))
                {  
                    type=*(PULONG)(objectheader+TYPE);
                    if(type==processtype)//表明是进程对象
                    {  
                        flags=*(PULONG)(object+FLAGS);//EPROCESS中Flags偏移量,指明了进程的死活
                        //KdPrint(("[GetPointerToObject]) flags:0x%x\n",flags)
                        if((flags&0xc)!=0xc)//死进程的flags最后一位为C
                        {
                            RecordProcess(object);
       num=num+1;  //进程数量
                        }      
                    }
                }
            }
        }
        
    }
   
}
//==================================遍历pspcidtable列举进程信息============================================
void ListProcess()
{
ULONG pspcidtable;
ULONG tablecode;
ULONG table1,table2,table3,table4,table5;  //2层表正常情况下足够存放进程了,所以3层表就不考虑

    ULONG cid;
ULONG NextHandleNeedingPool;

pspcidtable=GetAddrFromKpcr();  //取pspcidtable地址
KdPrint(("[ListProcess]pspcidtable:0x%x\n",pspcidtable));
if(MmIsAddressValid((PULONG)pspcidtable))//地址是否有效
{
  tablecode=*(PULONG)(*(PULONG)pspcidtable);//取tablecode地址
  NextHandleNeedingPool=*(PULONG)(*(PULONG)pspcidtable+0x038);//NextHandleNeedingPool值
  KdPrint(("[ListProcess] tablecode:0x%x\n",tablecode));
  KdPrint(("[ListProcess] NextHandleNeedingPool:0x%0x\n",NextHandleNeedingPool));  //PID最大值
  if((tablecode & 0x00000003)==0)//1层表存放的就是handle_table的基址
  {
   table1=tablecode;
   KdPrint(("[ListProcess] table1:0x%0x\n",table1));
   table2=0;
  }
  else if((tablecode & 0x00000003)==1)//2层表,一级表存放的是指向2级表的指针
  {   
   tablecode=tablecode&0xfffffffe;  //低二位清零
            table1=*(PULONG)tablecode;    //1个表存放0x800=2048大的PID,不够大  
   table2=*(PULONG)(tablecode+4);//2个表存放0x1000=4096大的PID,不够大
            table3=*(PULONG)(tablecode+8);//3个表存放0x1800=6144大的PID,不够大
   table4=*(PULONG)(tablecode+12);//4个表可以存放0x2000=8192大的PID,还不够大啊
            table5=*(PULONG)(tablecode+16);//5个表可以存放0x2800=10240的的PID,足够了吧
   KdPrint(("[ListProcess] table1:0x%x\n",table1));
            KdPrint(("[ListProcess] table2:0x%x\n",table2));
   KdPrint(("[ListProcess] table3:0x%x\n",table3));
   KdPrint(("[ListProcess] table4:0x%x\n",table4));
            KdPrint(("[ListProcess] table5:0x%x\n",table5));
  }
        //遍历
  
  for(cid=0;cid<NextHandleNeedingPool;cid=cid+4)//要加4
  {
   if((table1!=0)&&(cid<=0x800))//在第一个表中
   {
                GetPointerToObject(table1,cid);
   }
   if((table2!=0)&&(0x800<cid && cid<=0x1000))//在第二个表中
   {
    cid=(ULONG)(cid-0x800);
    //KdPrint(("[ListProcess] cid 0x%x\n",cid));
    GetPointerToObject(table2,cid);
                cid=(ULONG)(cid+0x800);
                //KdPrint(("[ListProcess] cid 0x%x\n",cid));
   }
   if((table3!=0)&&(0x1000<cid && cid<=0x1800))//在第三个表中
   {
    cid=(ULONG)(cid-0x1000);
    GetPointerToObject(table3,cid);
    cid=(ULONG)(cid+0x1000);
   
   }
   if((table4!=0)&&(0x1800<cid && cid<=0x2000))//在第四个表中
   {
    cid=(ULONG)(cid-0x1800);
    GetPointerToObject(table4,cid);
    cid=(ULONG)(cid+0x1800);
   }
   if((table5!=0)&&(0x2000<cid && cid<=0x2800))//在第五个表中
   {
    cid=(ULONG)(cid-0x2000);
    GetPointerToObject(table5,cid);
    cid=(ULONG)(cid+0x2000);
   }
  }
  
}

}
//
//取进程全路径====================================================================
//原理Eprocess->sectionobject(0x138)->Segment(0x014)->ControlAera(0x000)->FilePointer(0x024)->(FileObject->FileName,FileObject->DeviceObject)
void GetProcessPath(ULONG eprocess,CHAR ProcessPath[256])
{
ULONG object;
PFILE_OBJECT FilePointer;
UNICODE_STRING path;  //路径
UNICODE_STRING name;  //盘符
ANSI_STRING  string;
path.Length=0;
    path.MaximumLength=256;
path.Buffer=(PWCHAR)ExAllocatePoolWithTag(NonPagedPool,256,MEM_TAG);     //必须释放
if(MmIsAddressValid((PULONG)(eprocess+0x138)))//Eprocess->sectionobject(0x138)
{
  object=(*(PULONG)(eprocess+0x138));
        KdPrint(("[GetProcessFileName] sectionobject :0x%x\n",object));
  if(MmIsAddressValid((PULONG)((ULONG)object+0x014)))
  {
   object=*(PULONG)((ULONG)object+0x014);
   KdPrint(("[GetProcessFileName] Segment :0x%x\n",object));
   if(MmIsAddressValid((PULONG)((ULONG)object+0x0)))
   {
    object=*(PULONG)((ULONG_PTR)object+0x0);
    KdPrint(("[GetProcessFileName] ControlAera :0x%x\n",object));
    if(MmIsAddressValid((PULONG)((ULONG)object+0x024)))
    {
     object=*(PULONG)((ULONG)object+0x024);
     KdPrint(("[GetProcessFileName] FilePointer :0x%x\n",object));
    }
    else
     return ;
   }
   else
    return ;
  }
  else
   return ;
}
else
  return ;
    FilePointer=(PFILE_OBJECT)object;
    //KdPrint(("[GetProcessFileName] FilePointer :%wZ\n",&FilePointer->FileName));
ObReferenceObjectByPointer((PVOID)FilePointer,0,NULL,KernelMode);//引用计数+1,操作对象
RtlVolumeDeviceToDosName(FilePointer->DeviceObject,&name); //获取盘符名
//KdPrint(("[GetProcessFileName] FilePointer :%wZ\n",&name));
    RtlCopyUnicodeString(&path,&name);//盘符连接
RtlAppendUnicodeStringToString(&path,&FilePointer->FileName);//路径连接
//KdPrint(("[GetProcessFileName] FilePointer :%wZ\n",&path));
ObDereferenceObject(FilePointer);         //关闭对象引用
//需要转换成ANSI_STRING,然后在转换成char输出给ring3
RtlUnicodeStringToAnsiString(&string,&path,TRUE);    //释放内存
if(string.Length >= 256 ) //保证以\0结尾
{
  memcpy(ProcessPath, string.Buffer, 256);
  *(ProcessPath + 255) = 0;
}
else
{
  memcpy(ProcessPath, string.Buffer, string.Length);
  ProcessPath[string.Length] = 0;
}
ExFreePool(path.Buffer); //释放
RtlFreeAnsiString(&string);//释放
}
//====================================进程信息==========================================================
void  GetProcessInformation()//获取进程名,PID,路径
{
ListProcess();       //调用函数
KdPrint(("[DispatchIoctl] num:%d\n",num));//打印出进程数量
for(p=head;MmIsAddressValid(p);p=p->next)     //写实现功能,获取进程PID,进程名,图标、全路径
{   
  //KdPrint(("===============================================\n"));
  KdPrint(("[GetProcessInformation] EPROCESS:0x%x\n",p->addr));
  //EPROCESS获取进程名
  //XP下0x084偏移存放的进程PID
  //XP下0x174偏移存放的进程ProcessName
  p->pid=*(int *)(p->addr+CID);
  KdPrint(("[GetProcessInformation] PID:%d\n",p->pid));           //进程PID
  KdPrint(("===============================================\n"));
  memcpy(p->name,(PUCHAR)(p->addr+NAME),16);
  KdPrint(("[GetProcessInformation] ProcessName:%s\n",p->name));  //进程名   
  //获取全路径
  GetProcessPath(p->addr,p->Path);
  KdPrint(("[GetProcessInformation] ProcessPath:%s\n",p->Path)); //进程全路径
}
}
//==================================释放链表函数=========================================================
void FreeList()
{
    //释放链表,释放内存
PROCESSINFO *q;
    p=head;
q=p->next;
while(q!=NULL)
{
  KdPrint(("[DispathIoctl] p->addr:0x%x\n",p->addr));
  ExFreePool(p);
  p=q;
  q=p->next;
  n=n+1;
}
KdPrint(("[DispathIoctl] p->addr:0x%x\n",p->addr));
ExFreePool(p);
head=NULL;        //不知道ExFreePool释放后head竟然不为NULL.p和head都是全局变量
n=n+1;
KdPrint(("[DispatchIoctl] n:%d\n",n));//打印出释放进程数量
n=0;
}
2009-8-4 23:08
0
雪    币: 170
活跃值: (90)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
10
pspcidtable未导出结构保存了所有句柄的对象指针,只要获取这些指针地址就可以获取对象相关信息。
利用pspcidtable获取进程信息(进程名、进程PID、进程全路径、进程图标资源)
1.首先pspcidtable未导出需要获取地址 GetAddressPspcidtable()
2.pspcidtable如何获取句柄表的基址   GetTableBase()
3.pspcidtable记录的是对象指针,需要找出标志是进程对象的指针 GetProcessType()
4.如何找出进程对象 GetProcessObject()
5.利用什么结构记录进程对象地址   RecordProcessObject()
6.怎样遍历进程对象 ListProcessObject()
进程的object指向进程的EPROCESS,有了Eprocess就什么都有了。
2009-8-4 23:09
0
雪    币: 722
活跃值: (123)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
11
楼上放血了
GetProcessType()其实没必要这么麻烦,因为PsProcessType是导出的。
ListProcess()假定最多有五张表,结果得写那么多个分支,还是不和谐啊,如我说的用NextHandleNeedingPool来作上限就不用那么痛苦了。
2009-8-5 00:43
0
雪    币: 284
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
Mark,sd老师大放血。膜拜
2009-8-5 08:41
0
雪    币: 27
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
膜拜,膜拜,膜拜
2009-8-5 19:28
0
游客
登录 | 注册 方可回帖
返回
//