【计算机系统基础bomb lab】CSAPP实验:Bomb Lab
【计算机系统基础bomb lab】CSAPP实验:Bomb Lab
- CSAPP 实验:Bomb Lab
- 实验内容简述
- 实验环境
- 实验过程:phase 1
- phase 1 调试过程
- 实验过程:phase 2
- phase 2 调试过程
- 实验过程:phase 3
- phase 3 调试过程
- 实验过程:phase 4
- phase 4 调试过程
- 实验过程:phase 5
- phase 5 调试过程
- 实验过程:phase 6
- 一些其他收获
CSAPP 实验:Bomb Lab
实验内容简述
- 作为实验目标的二进制炸弹 “Bomb Lab” Linux可执行程序包含了多个阶段(或关卡),在每个阶段程序要求输入一-个特定字符串,如果输入满足程序代码所定义的要求,该阶段的炸弹就被拆除了,否则程序输出 “炸弹爆炸BOOM!!!” 的提示并转到下一阶段再次等待对应的输入-实验的目标是设法得出解除尽可能多阶段的字符串。
- 为完成二进制炸弹拆除任务,需要通过反汇编并分析可执行炸弹文件程序的机器代码或使用
gdb
调试器跟踪机器代码的执行,从中理解关键机器指令的行为和作用,进而设法推断拆除炸弹所需的目标字符串。
实验环境
64 位 linux 操作系统
实验过程:phase 1
将可执行文件
bomb
反汇编objdump -d bomb > bomb.asm
查看源代码
可以了解到
phase_1
是调用函数名在
bomb.asm
里搜索phase_1
,阅读源码含义
phase 1 调试过程
使用
gdb
调试,将断点设置在phase_1
,即0x400ee0
。运行后随便输入后查看0x402400
的内容(gdb) b *0x400ee0 Breakpoint 1 at 0x400ee0 (gdb) r Starting program: /mnt/d/OneDrive/csapp/bomb/bomb Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! dsasd Breakpoint 1, 0x0000000000400ee0 in phase_1 () (gdb) i r rip rip 0x400ee0 0x400ee0 <phase_1> (gdb) si 0x0000000000400ee4 in phase_1 () (gdb) si 0x0000000000400ee9 in phase_1 () (gdb) x/s 0x402400 0x402400: "Border relations with Canada have never been better."
可以从上面了解到字符串的答案就是:
Border relations with Canada have never been better.
实验过程:phase 2
查看
phase 2
汇编代码0000000000400efc <phase_2>:400efc: 55 push %rbp400efd: 53 push %rbx400efe: 48 83 ec 28 sub $0x28,%rsp400f02: 48 89 e6 mov %rsp,%rsi----------------------分割线----------------------------------------;上面是函数的初始化400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>;读取六个数字400f0a: 83 3c 24 01 cmpl $0x1,(%rsp);拿第一个数字与 1 进行比较400f0e: 74 20 je 400f30 <phase_2+0x34>;如果相等就跳转到 400f30400f10: e8 25 05 00 00 callq 40143a <explode_bomb>;否则,爆炸400f15: eb 19 jmp 400f30 <phase_2+0x34>400f17: 8b 43 fc mov -0x4(%rbx),%eax ;将第一个数字赋给 eax400f1a: 01 c0 add %eax,%eax;将第一个数字*2400f1c: 39 03 cmp %eax,(%rbx);将当前数字(eax) 与后一个数字 (rbx) 比较400f1e: 74 05 je 400f25 <phase_2+0x29>;如果相等,就跳转到400f25400f20: e8 15 05 00 00 callq 40143a <explode_bomb>;否则爆炸400f25: 48 83 c3 04 add $0x4,%rbx;循环迭代条件400f29: 48 39 eb cmp %rbp,%rbx;rbp 是栈底,也就是数组最后一个元素,作为循环判断的最后一个条件400f2c: 75 e9 jne 400f17 <phase_2+0x1b>;如果没遍历到最后一个数字,就跳转到循环内400f17400f2e: eb 0c jmp 400f3c <phase_2+0x40>;循环结束,跳转到最后函数结束后的处理400f3c400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx ;将第二个数赋值给 rbx400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp ;设置栈底,就是最后一个数字(0x18 == 24,输入的是6个数,所以是最后一个数)400f3a: eb db jmp 400f17 <phase_2+0x1b>;跳到 400f17 进入循环开始的地方----------------------分割线----------------------------------------;之后都是函数结束后的处理400f3c: 48 83 c4 28 add $0x28,%rsp400f40: 5b pop %rbx400f41: 5d pop %rbp400f42: c3 retq
phase 2 调试过程
设置断点
(gdb) b phase_2 Breakpoint 1 at 0x400efc
运行并输入phase 1 的答案与 phase 2 推测出的答案
一直单步汇编运行,执行到
400f0a: cmpl $0x1,(%rsp);
此步说明第一个数字必须为1比较第一个数字是否为 1
(gdb) x/wd $rsp 0x7ffffffedd70: 1
可以看到,
rsp
的值为1 ,就是我们输入的第一个值:1由此,通过
je 400f30
可知,两数相等,故跳转到400f30
通过单步汇编调试,查看
rsp
的值(gdb) si 0x0000000000400f30 in phase_2 () (gdb) si 0x0000000000400f35 in phase_2 ()
由此可以得出,
rbx
此时存放的是第二个数字2
,rbp
作为栈底,是最后一个数字的结尾,存放的是4199473
这个数字。继续单步汇编调试,进入循环
400f17
此时执行完
400f17:mov -0x4(%rbx),%eax
eax` 的值为 1。如下图继续单步调试,执行到
add %eax,%eax
,对eax
进行 乘 2 的操作。 此时eax
为2 ,与我们输入的第二个数(rbx
存放) 相等继续单步汇编调试,跳转到
400f25:add $0x4,%rbx
执行完
add $0x4,%rbx
,则选择了数组的下一个数字,判断循环是否是否结束。之后一直执行到结束。
所以答案为:
1 2 4 8 16 32
实验过程:phase 3
- 查看
phase_3
汇编代码:
0000000000400f43 <phase_3>:400f43: 48 83 ec 18 sub $0x18,%rsp400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ;第二个参数400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ;第一个参数400f51: be cf 25 40 00 mov $0x4025cf,%esi ;系统自带字符串"%d %d",2个参数400f56: b8 00 00 00 00 mov $0x0,%eax;----------------------------------------------------------------400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>400f60: 83 f8 01 cmp $0x1,%eax ;eax 与 1 比较。返回的是输入的参数个数400f63: 7f 05 jg 400f6a <phase_3+0x27> ;如果大于 1,跳转400f6a400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) ;比较 7 与 第一个参数大小400f6f: 77 3c ja 400fad <phase_3+0x6a> ;如果第一个参数大于7,爆炸400f71: 8b 44 24 08 mov 0x8(%rsp),%eax ;把第一个参数赋给 eax400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) ;跳转到 rax*8 + *0x402470400f7c: b8 cf 00 00 00 mov $0xcf,%eax400f81: eb 3b jmp 400fbe <phase_3+0x7b>400f83: b8 c3 02 00 00 mov $0x2c3,%eax400f88: eb 34 jmp 400fbe <phase_3+0x7b>400f8a: b8 00 01 00 00 mov $0x100,%eax400f8f: eb 2d jmp 400fbe <phase_3+0x7b>400f91: b8 85 01 00 00 mov $0x185,%eax400f96: eb 26 jmp 400fbe <phase_3+0x7b>400f98: b8 ce 00 00 00 mov $0xce,%eax400f9d: eb 1f jmp 400fbe <phase_3+0x7b>400f9f: b8 aa 02 00 00 mov $0x2aa,%eax400fa4: eb 18 jmp 400fbe <phase_3+0x7b>400fa6: b8 47 01 00 00 mov $0x147,%eax400fab: eb 11 jmp 400fbe <phase_3+0x7b>400fad: e8 88 04 00 00 callq 40143a <explode_bomb>400fb2: b8 00 00 00 00 mov $0x0,%eax400fb7: eb 05 jmp 400fbe <phase_3+0x7b>400fb9: b8 37 01 00 00 mov $0x137,%eax ;eax 为311(十进制)400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax ;比较第二个参数与 eax400fc2: 74 05 je 400fc9 <phase_3+0x86>;如果成功,拆弹成功400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>;----------------------------------------------------------------400fc9: 48 83 c4 18 add $0x18,%rsp400fcd: c3 retq
分析:
我这个都是自己调试出来看到参数的,最开始第一个参数与第二个参数理解有误。结果最开始输入的竟然是第二个参数。
首先看到了调用函数是
__isoc99_sscanf
可以从中得知,返回的eax
为输入格式的个数,而最开始0x4025cf
的字符串就是%d %d
,可以了解到参数个数为 2 。之后让第一个参数与 7 比较,可以猜到第一个参数是一个选择做一个选择,选择是下面7个跳转(类似于
switch
)。最后可以在
400f75:jmpq *0x402470(,%rax,8)
观测,程序的跳转地址情况。
phase 3 调试过程
设置断点并运行
此时的
ans.txt
如下:Border relations with Canada have never been better. 1 2 4 8 16 32 1 311
单步汇编调试,直到执行完
400f5b:callq 400bf0 <__isoc99_sscanf@plt>
单步汇编调试,执行到
400f75:jmpq *0x402470(,%rax,8)
,观察对应0x402470
开始的 8 个单元对应的值。由
rax
保存的是第一个参数的值,我们输入的是 1,所以计算机结果是0x402470 + 1 * 8
即0x402478
单元对应的值0x400fb9
。单步汇编调试,验证进入的单元。
要使第一个第二个参数与
0x137
相等,即十进制 311,最后拆除炸弹。
实验过程:phase 4
0000000000400fce <func4>:400fce: 48 83 ec 08 sub $0x8,%rsp400fd2: 89 d0 mov %edx,%eax ;eax = 14 (phase_4 输入的第三个参数)400fd4: 29 f0 sub %esi,%eax ;eax = 14 - 0 = 14 (第二个参数减去第三个参数)400fd6: 89 c1 mov %eax,%ecx ;ecx = 14400fd8: c1 e9 1f shr $0x1f,%ecx;逻辑右移31 位,ecx 为0400fdb: 01 c8 add %ecx,%eax ;eax = 14400fdd: d1 f8 sar %eax ;算术右移1位,即÷2 eax = 7400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx ;ecx = 7400fe2: 39 f9 cmp %edi,%ecx ;比较第一传入参数与 ecx = 7400fe4: 7e 0c jle 400ff2 <func4+0x24> ;如果小于等于7,跳转400ff2400fe6: 8d 51 ff lea -0x1(%rcx),%edx ;400fe9: e8 e0 ff ff ff callq 400fce <func4>400fee: 01 c0 add %eax,%eax400ff0: eb 15 jmp 401007 <func4+0x39>400ff2: b8 00 00 00 00 mov $0x0,%eax ;eax = 0400ff7: 39 f9 cmp %edi,%ecx ;比较 7 与第一个传入参数的大小400ff9: 7d 0c jge 401007 <func4+0x39>;如果第一个传入参数的大小大于等于7就正常返回400ffb: 8d 71 01 lea 0x1(%rcx),%esi400ffe: e8 cb ff ff ff callq 400fce <func4>401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax401007: 48 83 c4 08 add $0x8,%rsp40100b: c3 retq 000000000040100c <phase_4>:40100c: 48 83 ec 18 sub $0x18,%rsp401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx;第二个参数401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx;第一个参数40101a: be cf 25 40 00 mov $0x4025cf,%esi;"%d %d"40101f: b8 00 00 00 00 mov $0x0,%eax;------------------------------------------------------------------------------------------401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>401029: 83 f8 02 cmp $0x2,%eax;输入的参数个数是不是两个40102c: 75 07 jne 401035 <phase_4+0x29>;不是,爆炸40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp);将第一个参数和 0xe 比较401033: 76 05 jbe 40103a <phase_4+0x2e>;如果小于等于,就继续401035: e8 00 04 00 00 callq 40143a <explode_bomb>;否则爆炸40103a: ba 0e 00 00 00 mov $0xe,%edx;调用函数第三个传入的实参,1440103f: be 00 00 00 00 mov $0x0,%esi;调用函数第二个传入实参,0401044: 8b 7c 24 08 mov 0x8(%rsp),%edi;调用函数第一个传入实参,也是输入的第一个参数401048: e8 81 ff ff ff callq 400fce <func4>40104d: 85 c0 test %eax,%eax ;递归返回值为040104f: 75 07 jne 401058 <phase_4+0x4c>;否则爆炸401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) ;经过func4 递归后,输入的第二个参数应为0 401056: 74 05 je 40105d <phase_4+0x51>;第二个参数不为0就爆炸401058: e8 dd 03 00 00 callq 40143a <explode_bomb>;------------------------------------------------------------------------------------------40105d: 48 83 c4 18 add $0x18,%rsp401061: c3 retq
分析:
首先从
callq 400bf0 <__isoc99_sscanf@plt>
可以知道又是调用sscanf
,上面的内容就是输入的参数。401010:lea 0xc(%rsp),%rcx
和401015:lea 0x8(%rsp),%rdx
可以得知,这是传入的第二个和第一个参数,传入的是两个参数。之后从
40103a:mov $0xe,%edx
可以得知,接下来这三行都是传递实参,为调用func4
做准备。func4
被调用的过程可以分析出第一个数字小于等于7,之后完成以后回到phase_4
的401051:cmpl $0x0,0xc(%rsp)
可以得知第二个参数为 0。故测试以下7 0
。
phase 4 调试过程
设置断点,并运行至
phase_4
此时的
ans.txt
如下:Border relations with Canada have never been better. 1 2 4 8 16 32 1 311 7 0
执行到调用
func4
的位置,中间确认输入参数重点是
func4
的实现,这个过程需要的确定各个寄存器的值是否符合预期最后拆弹成功
实验过程:phase 5
0000000000401062 <phase_5>:401062: 53 push %rbx401063: 48 83 ec 20 sub $0x20,%rsp401067: 48 89 fb mov %rdi,%rbx ;第一个参数 -> rbx40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax ;接下来4行汇编是栈哨兵的检查(栈的保护机制,在编译时加上 -fno-stack-protector 就会被取消)401071: 00 00 401073: 48 89 44 24 18 mov %rax,0x18(%rsp) ;将哨兵的值赋值到栈里401078: 31 c0 xor %eax,%eax ;如果 eax 不为0,则说明栈被破坏;---------------------------------------------------------------------------------40107a: e8 9c 02 00 00 callq 40131b <string_length> ;判断字符串长度40107f: 83 f8 06 cmp $0x6,%eax ;字符串长度必须为6401082: 74 4e je 4010d2 <phase_5+0x70>401084: e8 b1 03 00 00 callq 40143a <explode_bomb>401089: eb 47 jmp 4010d2 <phase_5+0x70>40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx ;ecx = Mem[rbx + rax(0) * 1] 此时ecx 指向字符串第一个字符a40108f: 88 0c 24 mov %cl,(%rsp) ;取字符串的低8位值入栈401092: 48 8b 14 24 mov (%rsp),%rdx ;输入字符串作为值(具体的ascii),之后在一长串字符串将输入字符串的值作为偏移量401096: 83 e2 0f and $0xf,%edx ;与1111 取与运算后作为偏移量,也就是取低4位401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx ;edx = 'a' 0x4024b0 = "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?";分别取第10位 f(I:74->0100 1001), 第 16 位 l(O:79->0100 1111), 第 15 位 y(N:78->0100 1110),第 6 位 e(E:69->0100 0101),第 7 位 r(F:70->0100 0110),第 8 位 s(G:71->0100 0111)4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) ;rax = 04010a4: 48 83 c0 01 add $0x1,%rax ;rax = 14010a8: 48 83 f8 06 cmp $0x6,%rax ;循环终止的判断条件4010ac: 75 dd jne 40108b <phase_5+0x29>4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)4010b3: be 5e 24 40 00 mov $0x40245e,%esi ;第二个传入参数 0x40245e = "flyers"4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi ;第一个传入参数,rdi 0x10(%rsp)是保存的我们输入的地址,上面的操作就是将每个字符串赋值到0x10(%rsp)起始的地址单元里面4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal> ;要使得二者相等4010c2: 85 c0 test %eax,%eax4010c4: 74 13 je 4010d9 <phase_5+0x77> ;相等就结束4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)4010d0: eb 07 jmp 4010d9 <phase_5+0x77>4010d2: b8 00 00 00 00 mov $0x0,%eax ;初始化 eax 为0 4010d7: eb b2 jmp 40108b <phase_5+0x29>;---------------------------------------------------------------------------------4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax4010e5: 00 00 4010e7: 74 05 je 4010ee <phase_5+0x8c>4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>4010ee: 48 83 c4 20 add $0x20,%rsp4010f2: 5b pop %rbx4010f3: c3 retq
分析:
首先能够判断是要输入 6 个字符串,然后随便输入6个字符进行调试,建议使用规律不同的字符串,这样能够知道传递的参数位置。我使用的是
abcdef
。接着当代码运行到
40108b:movzbl (%rbx,%rax,1),%ecx
以后,表示程序进入到循环体内,最后退出的条件是rax == 6
,而rax
的初始值为 0, 这个意思就是遍历完我们输入的字符串,循环体内的意思是取我们输入字符的值的低四位的值,我们输入的是字符,也就是该字符对应ascii
码的低四位。然后利用这个值作为一个索引,去0x4024b0
这个长的字符串中寻找f
、l
、y
、e
、r
、s
对应的下标,注意我注释里面添加说明的是第几位,但是具体下标要-1
操作。当代码运行到
4010b3:mov $0x40245e,%esi
的时候,可以看到这个就是在传递参数了,作为第二个实参,是系统赋予的,要将这个字符串flyers
和0x10(%rsp)
的值(也就是上面提及到的低四位ascii
码作为索引取出来的字符)进行比较,必须要二者相等。
综上,得出更新的ans.txt
Border relations with Canada have never been better.
1 2 4 8 16 32
1 311
7 0
IONEFG
phase 5 调试过程
设置断点在
phase_5
并运行运行到进入循环体
40108b:movzbl (%rbx,%rax,1),%ecx
可以看到
movzbl (%rbx,%rax,1),%ecx
,注意rbx
最开始就用存储输入的字符串,此时rax
作为循环体初始变量 为 0。执行到
401096
以后,完成了取第一个值的低四位的目的,此时再查看取到值是否为I
的低四位ascii
码的值 9可以看到值为 9,证明计算没错,之后可以直接运行
n
,直到结束。运行
n
,直接验证是否正确
实验过程:phase 6
实在是太长了,暂时放弃
【计算机系统基础bomb lab】CSAPP实验:Bomb Lab相关推荐
- 计算机系统基础:时序逻辑电路实验
一.实验目的 1.掌握集成触发器的逻辑功能及其应用 2.了解移位控制的功能及其工作原理 二.实验设备与器材 1.+5V直流电源 2.连续脉冲源 3.单次脉冲源 4.逻辑电平开关 5.逻辑电平显示器 6 ...
- 计算机系统基础实验:认识logisim软件、门电路逻辑功能测试(仿真)
通过logisim对逻辑电路进行分析 文章目录 目录 文章目录 前言 一.使用工具 二.实验过程 1.门电路绘制 2.真值表 总结 前言 计算机系统基础也开了实验课,实验内容是利用logisim软件进 ...
- CSAPP实验记录(二)Bomb Lab
CSAPP实验记录(二)Bomb Lab 二进制炸弹是由一系列阶段组成的程序.每个阶段都要求你在 stdin 上键入一个特定的字符串.如果你输入了正确的字符串,那么这个阶段就被拆除,炸弹进入下一个阶段 ...
- CSAPP实验二:二进制炸弹(Bomb Lab)
本系列文章为中国科学技术大学计算机专业学科基础课<计算机系统>布置的实验,上课所用教材和内容为黑书CSAPP,当时花费很大精力和弯路,现来总结下各个实验,本文章为第二个实验--二进制炸弹( ...
- CSAPP实验之Bomb Lab详解
前言 Bomb Lab来自<深入理解计算机系统>(CSAPP)一书的第三章"程序的机器级表示"的配套实验,该实验的目的是通过反汇编可执行程序,来反推出程序执行内容,进而 ...
- CSAPP实验二——bomb lab实验
CSAPP实验二-- bomb lab实验 实验前准备 第一部分(phase_1) 第二部分(phase_2) 第三部分(phase_3) 第四部分(phase_4) 第五部分(phase_5) 第六 ...
- 深入理解计算机系统(CSAPP)含lab详解 完结
文章目录 深入理解计算机操作系统-第一章 1.1 信息就是位 + 上下文 1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令 1 ...
- csapp实验摘选 I Data Lab ——小小菜下士的第一篇博客
csapp实验摘选 I Data Lab --小小菜下士的第一篇博客 注:这是我的第一篇博客,试图在通往程序猿的路上踏出坚实的一步. --小小菜下士 实验来自: [读厚 CSAPP]I Data La ...
- CSAPP Lab5实验记录 ---- Shell Lab(实验分析 + 完整代码)
文章目录 Lab 总结博客链接 前引 Lab5 Shell Lab 1.获取相关Lab材料 2.Overview(总览) 3.Explore(实现前的摸索) 4.函数实现 + 实现代码分析 1.eva ...
最新文章
- 图解 Kafka,一目了然!
- 【Linux 驱动】第九章 与硬件通信
- 训练深度学习网络时候,出现Nan是什么原因,怎么才能避免?——我自己是因为data有nan的坏数据,clear下解决...
- Web.config 灵活配置
- 有勇气的牛排---微信小程序
- php 关于文件的一些封装好的函数
- 《BeagleBone开发指南》——1.7 小结
- python的运行环境_python-运行环境配置-1
- 清华CrossWOZ,助你徒手搭建任务导向对话系统
- linux把标准输出赋值给变量遇到的问题
- Vue 实现点击复制功能概述
- 在创投界有个公开的秘密
- 解决debian (Friendly ARM 嵌入式板)的sudo等一部分命令无法TAB补全
- Spring MVC拦截器配置以及统一登陆校验实现
- 如何清理驱动人生的新闻弹窗
- [转载]INNO SETUP注册DLL文件
- 网页加速器1.0.5.6 免费版
- android studio导入as项目,Android Studio(AS)--导入项目
- 《惢客创业日记》2018.11.23(周五) 郭鑫年,你是不是死了?
- C++ : Boost : Rational 有理数类