我爱秘籍论坛

 找回密码
 加入论坛

QQ登录

只需一步,快速开始

扫一扫,访问微社区

CE:最优秀的游戏修改工具入门:修改器制作傻瓜教程说说:你最想要找的修改器以前游戏玩我们,现在我们玩游戏
进阶:游戏修改视频教程导航必备:超2400款游戏秘籍下载:超5000款游戏修改器必读:论坛金币获得方法
查看: 447805|回复: 37

[分享] CEAA教程第二季:A Very In Depth Tutorial on Auto Assembler: Extended (译)

[复制链接]
发表于 2014-7-31 12:09:13 | 显示全部楼层 |阅读模式
本帖最后由 小白兔寻保护 于 2014-8-2 15:19 编辑

写在前面:


这篇文章是论坛置顶教程:CEAA教程:A Very In DepthTutorial on Auto Assembler(译)的第二季,也是一些很基础的知识,供初学者参考。当年英语盲的我苦等了一万年,也没有等来翻译。现在英语好了,身体也壮了,想起当年到处找资料的幸酸,就为大家做点好事,把这个文章翻译一下吧。
不得不说cn_zou这篇CEAA教程:A Very In DepthTutorial on Auto Assembler(译)翻译地太棒了,精美的排版,恰当的翻译,让我第一次笑着读完教程。致敬一下!由于本文是其姐妹篇,所以我会在风格上尽可能模仿。

当然,本人英文,语文,美术的水平有限,如果引起大家不满,我会诚恳地对大家道歉:对(ni)不(ma)起(bi)。
------------------------------------------
索引:
------------------------------------------
i) 介绍
1) 了解一下code-caves
2) LoadBinary()函数
3) 了解标志(flags)
4) 深入了解寄存器
5) 巧用Call指令来修改EIP
6) db,dd,dw
7) 在AA脚本中使用负值
8) 堆栈
9) 使用CE调试器E来调试AA脚本


---------------------------------------
i:介绍
---------------------------------------
目前已经有很多不错的AA基础教程了,但是它们并没有涉及到一些最新的资料。这篇教程里面的资料都是我目前正在作的研究的一些东西,不易解决。很多人不知道怎么来着手这些问题(或者知道存在这些问题,可并没有深入研究),所以我对这些问题进行了搜集整理。
PS:这篇教程比起之前我写的那篇更加深入一点。如果没有读过之前那篇,那么再继续研究之前,我强烈建议你去读一读它。当然,有些内容我们会进行回顾,不过~~~还是看看吧亲!


---------------------------------------
1) 了解一下code-caves
---------------------------------------
我们从code cave的定义开始!那么,什么是codecave呢?
Codecave是一段放置在未使用内存区中的代码,从一段使用中内存开始执行。有点绕口吧~那么我们来个例子吧~
假设我们要修改一个游戏,现在我们找到了一行代码,比如,我们要改HP,假设代码如下:
  
  Code:
  
  
sub [esp+50],ecx
  


如果我们想使用一个变量来保存当前最大HP的值,以便在注入时从这个变量中读取到这数值用来写入当前HP中,使得血量始终保持最大。假设这个变量的地址是0xDEADBEEF。
  
  Code:
  
push eax // 保存eax的值,因此我们不会玩坏任何东西
  mov eax,[DEADBEEF] //将0xDEADBEEF的值存入eax
  mov [esp+50],eax //将eax的值,也就是DEADBEEF的值放入HP值中
  pop eax //恢复eax
  
  
现在我们已经建立起了框架,假设指令sub [esp+50],ecx 的地址是0x00123456。我们的codecove脚本看上去应该是这样:
  
  
  Code:
  
  
  
  alloc(our_codecave,1024) //1kb足矣!甚至过于大了,不过,呵呵
  label(return)
  
  00123456: //减法指令的地址,之前假设过它
  jmp our_codecave //跳到我们需要被执行的代码
  return: // 这个待会儿解释
  
  our_codecave: // 在这儿定义它
  push eax
  mov eax,[DEADBEEF]
  mov [esp+50],eax
  pop eax //似曾相识?
  jmp return // 稍后解释
  

(注意一下,这么写是不对的,因为sub指令占用4个字节,但是jmp指令是5字节的--看看我的其他教程吧,可以找到一些信息~)


我说过我会解释return那条代码的,现在是时候说明一下了。这么做是让CE知道在哪里让你的代码返回。要想计算每一样东西的发展与变化是一件极痛苦的过程。CE提供了这个简便的功能,使得你不会陷入那种痛苦中。我们在return处做了一个标志,取名也叫return,于是在CE的信息收集容器中会记录下这个记号。因此当我们写“jmp return”时,它会将return替换成return所代表的地址。


当我们label了return后,它会代表地址0012345B(00123456+ 5),因此CE记录下了这一信息,当执行到”jmpreturn”时,会自动替换成”jmp 0012345b”。



  You  wrote:
这tm和代码注入有什么区别?

我觉得这好像就是代码注入吧。。。

评分

参与人数 3金币 +52 鲜花 +2 收起 理由
magicway + 1 感谢您的分享!
我爱秘籍 + 50 + 2 原创作品,加分鼓励!
霸气、小老头℡ + 1 感谢您的分享!

查看全部评分

 楼主| 发表于 2014-7-31 12:15:25 | 显示全部楼层
Code:
------------------------------------------
|    字节    |       描述          | 类型 |
| 序号     |                         |       |
|-----------|---------------------|----- |
|    0       |       进位标志      | ST  |
|    1       |         保留         | RE   |
|    2       |       奇偶标志    | ST   |
|    3       |         保留         | RE   |
|    4        |    辅助进位标志  | ST   |
|    5       |         保留         | RE   |
|    6       |         零标志      | ST   |
|    7       |         符号标志   | ST   |
|    8       |         陷阱标志   | ST   |
|    9       |       中断标志     | SY   |
|   10      |       方向标志    | CO  |
|   11      |       溢出标志     | ST   |
|   12       |    I/O 权限级别  | SY   |
|   13      |       嵌套任务    | SY   |
|   14      |         保留         | RE   |
|   15      |       恢复标志    | SY   |
|   16       | 虚拟-8086 模式 | SY   |
|   17       |       定位检查   | SY   |
|   18       |    虚拟中断标志 | SY   |
|   19       |     虚拟中断等待 | SY   |
|   20      |         保留         | RE   |
|   21      |         保留         | RE   |
|   22      |         保留         | RE   |
|   23      |         保留         | RE   |
|   24      |         保留         | RE   |
|   25      |         保留         | RE   |
|   26      |         保留         | RE   |
|   27      |         保留         | RE   |
|   28      |         保留         | RE   |
|   29       |         保留         | RE   |
|   30      |         保留         | RE   |
|   31      |         保留         | RE   |
---------------------------------------------


KEY:
ST – 状态标志
RE - 保留标志 尚未使用
CO – 控制标志
SY – 系统标志


当你想对标志做手脚时,你可以来查查这个表




回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:19:19 | 显示全部楼层
-------------------------------
9) 使用CE来调试一个脚本
------------------------------



首先你要有一个程序来调试,当然你也要有一只CE^ω^
好的,我们眼前出现了一只野生的扫雷。

嗯嗯,在我们进行实战之前,我想要说明一些理论上的东西。首先我们必须要得到各种需要的信息,所以一个好的调试器必须要能查看所有的寄存器,标志和段,还必须能单步执行指令。调试的方式是在某个地址上下一个断点,当程序执行到这个断点的位置时,调试器将取得该程序的控制权,此时,调试器就能干各种坏事了。

那么单步调试是怎么回事呢?就是一步一步地运行程序的每一个指令,并且即时地查看每一步的变化

这会你可以在程序运行时查看它的运行状况,把下面的代码注入到扫雷之中:


  
  Code:
  
  
  alloc(codecave,1024)
  label(return)
  
  01002ff5:
  jmp codecave
  nop
  return:
  
  codecave:
  push eax
  
  mov eax,4
  nop
  
  cmp eax,3
  nop
  
  cmp eax,4
  nop
  
  pop eax
  
  jmp return
  

CE会弹出一个消息框告诉你代码注入成功


  
  Code:
  
  
  The code injection was successfull //汉化版的CE会提示:代码注入已经成功
  codecave=x  
  

这个xalloc随机分配的一段内存的首地址。

点击OK,自动汇编窗口就会关闭,在内存浏览器中跳转到刚刚的x地址处,你将会看到你的代码已经摆在了里面。

我只写了一些简单的代码在那儿,很容易就能查看它们。

选择最顶端的地址x,再点击调试->切换断点。CE可能会询问你一些废话,狂点确定即可。地址x接下来会变成绿色,接下来我们切换到扫雷,然后随便点一个方块来激活断点。

让我们回到理论的情况,来看看扫雷接下来干了些什么:

  
  Code:
  
  
  user clicks 用户点击
  blah blah, time increasing starts ….开始计时
  increase time address (01002ff5)  says jmp x 增加时间值(地址在01002ff5) 然后设跳到了x
  goto x 跳到x处执行
  Cheat Engine has breakpoint at x x处的断点处中断,CE取得控制
  STOP 停止
  
  
  
现在看看发生了什么,扫雷现在处于冻结状态。如果你试图关闭扫雷,系统将会提示你程序失去响应,再跟你扯一堆蛋。
  
  回到CE,一些标签已经变成了红色。这表示这些东西已经和你上次看到它们时不一样了(可能有很多数据被改变了)
  
  接下来,点击调试->步入(F7),然后继续多步入几次。察看标签们是如何变化的。
  
  
你敢说这不是很神奇吗?
  
  
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
  
  Credits参与人员:
  
  Intel - Creating the instruction  set reference pdfs Intel – 提供了这些指令的参考文本:  
  
  Volume 1 1
  Volume 2 2
  
  Dark Byte - Creating Cheat Engine,  along with Auto Assembly, and helping me out with LoadBinary (PM, though  there is the manual, that I forgot about)
  Dark Byte –CE的作者,在AA道路上指引了我,并且帮助我解决了关于LoadBinary的疑惑(尽管文档中有说明,不过我忘了~)
  
  x0r - For helping me understand the  CRC  bypass for MapleStory (forum post here). Xor,帮助我绕过了枫之谷的循环校验。
  


[发帖际遇]: 打了通宵麻将, 赢得 2 金币. 幸运榜 / 衰神榜
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:11:26 | 显示全部楼层
-----------------------------------
3)深入了解flags
-----------------------------------


首先,让我们快速浏览一遍一些常见的标志.


(The Carry Flag)进位标志:
当一个无符号溢出发生时,这个标志置1.这是什么意思?比如255+5,或者200+60,这两种情况都会使得和超过255,这是2byte的最大值。当未产生溢出时,这个标志会被置为0。

(The Zero Flag)零标志:
这个标志会在某个运算操作结果为0时被置1(稍后我们会说到它在比较运算中的作用)。当然,这个标志为0时的结果不解释。
(The Sign Flag)符号标志:
这个标志会在某个运算的结果为负时被置1,反之亦然。
(The Overflow Flag)溢出标志:
这个标志和进位标志差不多,不过它用于有符号溢出,也就是值处于-128 或 127之间。
(The Parity Flag)奇偶标志:


这个标志很容易使人混淆,至少我是这么觉得。但首先你要清楚,这个标志针对二进制运算,它会在二进制运算结果位偶时置1.举个例子,如果有次运算的结果为4,它的二进制形式为100.这个数的bit数有多少呢?3.所以此时标志置0。如果换成50,即二进制的110010,这一位将会置1.
(The Adjust Flag)辅助进位标志:        
通常用于对BCD算术运算结果的调整.


~~~~~~~~~~~~~~~~~~~~~~~~~~~~
好吧,这样应该熟悉它们了吧?
你当然可以用ce来修改标志位,可如果想在脚本里修改该怎么做呢?


  
  Code:
  

push eax
  
mov eax,3
  
cmp eax,3
  
pop eax
  
对你没有看错。4行代码。
你会问,没有更简单的办法了吗?


至少我不知道~
显然利用通样的手法,我们能够解决其他标志的置位。搞清楚你要搞的标志位的变化规则就行啦~(push/pop)


~~~~~~~~~~~~~~~~~~~~~~~~~
我记得我说过要解释一下零标志是怎样影响比较指令的。
首先,你需要知道比较指令cmp是如何工作的,这个指令的行为很像sub指令,举个例子,如果你要比较2和3。
  
  Code:
  
  
cmp 2,3
  


(注意,实际上你不能这么使用这个指令,我们只讨论理论上的情形……)
PF、AF、SF都是缩写,看看完整形式的英语应该很容易分辨吧~
PF、AF和SF会被置1,现在我们进一步说明一下(回到我们的定义处)
[PF] 好像对它没有什么影响吧?
[AF]溢(he)出(he)了?
[SF] 哟西,结果当然为负拉
(这里我用CE来进行的调试,稍后我们讨论这个。)
让我们再算一个。
  
  Code:
  

cmp 4,3
  
(再次说明,咱这是理论上的情况)


[PF] 只有一位,当然置1了。
我们想想这个,4-3=1,对吧?1在二进制中就是1,对吧?真是令人愉快的巧合啊=P
~~~~~~~~~~~~~~~~~~~~~


你真的会把游戏玩坏的!让我们假设一个游戏要判断你是否已经死亡:
  
  Code:
  
cmp [playeraddress],0
  


如果你在这代码上使用一个codecave,它看起来可能会是这样:


  
  Code:
  
cmp [playeraddress],0
  
push eax
  
mov eax,2
  
cmp eax,1
  
pop eax
  
jmp whatever
  
你现在已经清楚了零标志。这为什么很重要?
现在假设你的HP已经变成0了


  
  Code:
  
cmp 0,0
  
0 - 0 = 0,是吧。这会设置0标志,告诉游戏你已经挂了。然而,如果你手工清楚了零标志,游戏就不能确定你到底死没死了。
如果你想了解更多关于运算指令如何影响标识符,看看附录A,我在Intel参考手册卷1里面查到了一些并写在了里面。

回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:10:28 | 显示全部楼层
-------------------------------------------
2): LoadBinary() 函数
-------------------------------------------


LoadBinary是咩啊?要想理解它,就要看看CE代码中的这个:
  
  Code:
  
  
  
  
  
  
1229             begin
  
1230               binaryfile:=tmemorystream.Create;
  
1231               try
  
1232                 binaryfile.LoadFromFile(loadbinary.filename);
  
1233             ok2:=writeprocessmemory(processhandle,pointer(testdword),binaryfile.Memory,binaryfile.Size,bw);
  
1234               finally
  
1235                 binaryfile.free;
  
1236               end;
  
1237             end;
  
伪代码:
         创建一个文件对象;
         从路径(loadbinary.filename)加载文件;
         写进程内存(进程句柄,指针,文件对象,二进制文件大小,bw)
         释放文件




所以,基本上就是把一个满是汇编代码的二进制文件的内容加载进目标进程的内存中的一个特定位置。
那么这个在哪里用得上?
LoadBinary()用来欺骗游戏,让它以为它的内存没有被修改过。举个例子,枫之谷游戏脚本中,我们使用LoadBinary来载入代码,用一个codecave跳到代码处,这样就可以跳过游戏代码的循环校验(CRC)。



回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:14:44 | 显示全部楼层
----------------------------------------------
4)深入寄存器
----------------------------------------------


第一篇教程里已经提到过了一些常见的寄存器,接下来我们说一些特殊的寄存器:
这里列出的两个特殊寄存器,都不可以被直接修改。
它们是EIP寄存器和EFLAGS寄存器。
手册中可以查到它们的定义:
EFLAGS Register
32位的EFLAGS寄存器包含了一组状态标志,一个控制标志,和一组系统标志…
INSTRUCTION POINTER 指令指针
这个指令包含当前代码段在下一个将被执行的指令地址的偏移。
说的更细点:
这个寄存器同其他寄存器一样,一共有32位,例如像下面一样:
01101010101001010101010100101010
这是32位,或者说是32个字节数据,每一位的值可以是0或1.
基本上EFLAGS寄存器里的每一个字节都指代一个明确的标志位,下面的表格说明了每一个字节代表的标志:





回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:15:53 | 显示全部楼层
如果你想动动手脚,你可以像这样创建一个codecave




  Code:


codecave:
// 这里留下了原始代码,能让你不至于把所有东西弄的一团糟
push eax
pushf //EFLAGS保存到堆栈中
pop eax // EFLAGS的数据放入eax
//现在eax里面就包含了EFLAGS的数据拉
or eax, // 设置eax的第6
//6th bit of EAX
//ZF is 6 记住,ZF零标志位是第六位。
push eax
popf
nop // 这里只是为了调试方便打了个nop(后面有讨论哦~)
popf
pop eax
jmp return //从哪来回哪去


当然,直接用cmp指令置0的方法来搞,条条大路通罗马嘛~并且,这会让你更聪明~


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在我们详细说明EIP寄存器。首先,EIP寄存器不能直接修改。阅读下一节就会知道为什么了。我们再次回到它的定义:


EIP: 这个指令包含当前代码段在下一个将被执行的指令地址的偏移。


基本上所有程序都有着不同的代码段。EIP寄存器会获取当前代码段的基址,再拼凑成下一个要被执行的地址。这使得进程知道下一步应该执行哪里。

回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:16:28 | 显示全部楼层
-------------------------------
5) 使用Call指令来修改EIP
-------------------------------
首先,你要清楚EIP是不能够被直接修改的。
如果你继续探索,你可以想下面这样做:




  
  Code:
  
  

  
  
  
label(lolJump)
  
  
codecave:
  
push eax //save eax
  
  
call lolJump
  
  
lolJump:
  
pop eax //pop EIP into eax
  
  
mov [eax-6],90909090 //eax =  &"pop eax" (& = C++ for "address of")
  
mov [eax-1],90
  
  
pop eax
  
  
jmp return
  
这会在codecave+1处执行一个db 90 90 90 90(就是call的起始处),通过这种方式,你实际上可以随意修改代码。

回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:17:41 | 显示全部楼层
---------------------------------------
6)db dd和dw
---------------------------------------


首先,我们可以通过下面的这个操作表把事情搞清楚:


  
  Code:
  

define byte       db
  
define word      dw
  
define dword    dd
  
这些理解起来很容易;这些东西仅仅是修改当前地址上的数据,例如,db 90会在当前地址上写入nop指令,因为90就是nop的机器码。
你也可以这样:




  
  Code:
  
db 83 7d 0c 00
  
它会被识别为:
  
  Code:
  
cmp dword ptr [ebp+0c],00
  


我们来看看db所代表的字节长度。


  
  Code:
  

dw 837d 0c00
  
它们是一些不同的操作码,来看看dw所代表的字节长度。


  
  Code:
  
dd 837d0c00
  
你可以使用这个东西来缩短你的脚本(如果这样做的话,注释是必须的,否则你会迷失在代码的汪洋之中)




  
  Code:
  
cmp dword ptr [ebp+0c],00
  

肯定会比下面那串东西容易懂吧
  
  Code:
  
dd 837d0c00
  
这里有个例子
假如你想改变0xDEADBEEF处的操作码


xor eax,eax


xor eax,ebx


你可以写个这样的脚本:
  
  Code:
  
[ENABLE]
  
  
DEADBEEF:
  
xor eax,eax
  
  
[DISABLE]

  
DEADBEEF:
  
xor eax,ebx
  
  


或者你也可以像这样做(这会对AOB Scan有帮助的,我想)

  
  Code:
  

  [ENABLE]
  
  DEADBEEF:
  db 31 c0
  //xor eax,eax
  
  [DISABLE]
  
  DEADBEEF:
  db 31 d8
  //xor eax,ebx
  
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:18:14 | 显示全部楼层
---------------------------------
7) AA脚本中使用负数
------------------------------------------------------------------

要想在AA脚本中使用负数,你只要将这个数置反,并+1.
让我们看看下面这个例子:

首先,-1在汇编中到底是什么样子的?

我们试试按开始提到的步骤来试试,首先我们得到1的二进制形式,当然,就是“00000001.
然后,我们置反它们,
得到:
  
  Code:
  
  
  11111110
  
  

接下来将它+1
  
  Code:
  
  
  11111111
  
  

我们将它转换成16进制的形式,也就是0xFF。
到此为止,我们就得到了-1在汇编中的形式,也就是

  
  Code:
  
  
  0xFF
  
  


那么-5呢?
我们取5的二进制形式:
  
  Code:
  
  
  
00000101
  
将它置反:
  
  Code:
  
  
  
11111010
  
再加上1
  
  Code:
  
  
  
11111011
  
转换成16进制:
  
  Code:
  
  
  
FB
  

所以,答案便是0xFB

最后一个例子,如果我们想在al寄存器里面赋值-10要怎么做呢?
首先我们要根据上面所说的方法得到-1016进制,即0xFB
所以你的操作码应该是这样:

  
  Code:
  
  
  
mov  al,0xFB
  

回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-7-31 12:18:42 | 显示全部楼层
-----------------------------------
8) 堆栈
-----------------------------------
堆栈其实是个很简单的概念。大概说来就是一堆能够被ESP寄存器访问的值。开始时,我会讨论压栈与出栈,随后我们讨论使用ESP来访问堆栈。压栈与出栈能够让你初步理解堆栈,之后对ESP寻址的探究会让你的理解更进一步。
如果你不用ESP的话,你可以对堆栈干两种羞羞的事情,一种是push,压栈,即向堆栈中压入数据;另一种就是出栈,其实就是和压栈反着干,把东西拿出来。压栈操作使得一个值被压入堆栈的顶端,而出栈操作使得一个值从堆栈中拿出。
举个例,我们假设这就是我们的堆栈:
  
  Code:
  
  
  1
  2
  3
  4
  5
  6
  7
  8
  9
  
如果我们执行以下代码:
  
  Code:
  
  
  push 0
  
我们的堆栈会变成这样:
  
  Code:
  
  
  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
  
再回到之前的那个堆栈
如果我们执行这个代码
  
  Code:
  
  
  
pop  eax
  
我们的堆栈就会变成这样

  
  Code:
  
  
  2
  3
  4
  5
  6
  7
  8
  9
  

然后eax的值会变成”1”,就是刚刚出栈的数据

请注意,如果你这么干的话
  
  Code:
  
  
  
push  eax
  



堆栈的顶部不会变成”eax”这几个字,而是会被压入eax里面保存的那个值




现在,我们来试试用esp访问堆栈中的数据:
Esp是保存指向栈顶指针的寄存器,假设我们有这样一个堆栈

  
  Code:
  
  
  1
  2
  3
  4
  5
  



于是我们可以得出以下等价形式:

  
  Code:
  
  
  [esp+ 0] = 1
  [esp+ 4] = 2
  [esp+ 8] = 3
  [esp+12] = 4
  [esp+16] = 5
  
这并不难懂吧,但是善用ESP会让对堆栈的访问更加灵活。我们在esp的地址+4,因为每个堆栈中的数据长度为4byte,所以每加上一个4,指针就指向了下一个数据。
回复 支持 反对

使用道具 举报

发表于 2014-7-31 20:00:09 | 显示全部楼层
来加我们群吧...
群号:171937438
回复 支持 反对

使用道具 举报

发表于 2014-8-1 00:00:40 | 显示全部楼层
论坛都没人了吗= =
[发帖际遇]: 霸气、小老头℡ 不小心在路边拣到一个信封, 发现里面原来有 4 金币. 幸运榜 / 衰神榜
回复 支持 反对

使用道具 举报

发表于 2014-8-1 10:32:48 来自手机 | 显示全部楼层
默默地顶一下,楼主辛苦了
回复 支持 反对

使用道具 举报

发表于 2014-8-2 15:11:24 | 显示全部楼层
万分感谢楼主,另外祝楼主单棍节快乐
回复 支持 反对

使用道具 举报

发表于 2014-8-2 15:17:14 | 显示全部楼层
好教程,谢谢分享。
回复 支持 反对

使用道具 举报

发表于 2014-8-18 16:02:34 | 显示全部楼层
谢谢
回复

使用道具 举报

发表于 2014-10-19 19:45:41 | 显示全部楼层
给力的作品
回复 支持 反对

使用道具 举报

发表于 2015-1-25 16:57:40 | 显示全部楼层
幸苦了
回复

使用道具 举报

发表于 2015-3-15 00:07:06 | 显示全部楼层
万分感谢
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入论坛

本版积分规则

QQ|Archiver|手机版|小黑屋|我爱秘籍论坛  

GMT+8, 2019-7-17 21:37 , Processed in 0.172323 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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