Microcorruption CTF Cusco Write-up

Microcorruption CTF Cusco Write-up

- 8 mins

Summary:

This is a write-up of my solution to the Microcorruption CTF challenge “Cusco” (LOCKIT PRO r b.02).

This version claims to fix the conditional flag overwrite issue that we exploited in the last challenge:

This is Software Revision 02. We have improved the security of the
lock by  removing a conditional  flag that could  accidentally get
set by passwords that were too long.

This indeed appears to be the case. Let’s check out the entire program and then see if we can figure out what’s going on:

0010 <__trap_interrupt>
0010: 3041 ret
4400 <__init_stack>
4400: 3140 0044 mov #0x4400, sp
4404 <__low_level_init>
4404: 1542 5c01 mov &0x015c, r5
4408: 75f3 and.b #-0x1, r5
440a: 35d0 085a bis #0x5a08, r5
440e <__do_copy_data>
440e: 3f40 0000 clr r15
4412: 0f93 tst r15
4414: 0724 jz #0x4424 <__do_clear_bss+0x0>
4416: 8245 5c01 mov r5, &0x015c
441a: 2f83 decd r15
441c: 9f4f d445 0024 mov 0x45d4(r15), 0x2400(r15)
4422: f923 jnz #0x4416 <__do_copy_data+0x8>
4424 <__do_clear_bss>
4424: 3f40 0000 clr r15
4428: 0f93 tst r15
442a: 0624 jz #0x4438 <main+0x0>
442c: 8245 5c01 mov r5, &0x015c
4430: 1f83 dec r15
4432: cf43 0024 mov.b #0x0, 0x2400(r15)
4436: fa23 jnz #0x442c <__do_clear_bss+0x8>
4438 <main>
4438: b012 0045 call #0x4500 <login>
443c <__stop_progExec__>
443c: 32d0 f000 bis #0xf0, sr
4440: fd3f jmp #0x443c <__stop_progExec__+0x0>
4442 <__ctors_end>
4442: 3040 d245 br #0x45d2 <_unexpected_>
4446 <unlock_door>
4446: 3012 7f00 push #0x7f
444a: b012 4245 call #0x4542 <INT>
444e: 2153 incd sp
4450: 3041 ret
4452 <test_password_valid>
4452: 0412 push r4
4454: 0441 mov sp, r4
4456: 2453 incd r4
4458: 2183 decd sp
445a: c443 fcff mov.b #0x0, -0x4(r4)
445e: 3e40 fcff mov #0xfffc, r14
4462: 0e54 add r4, r14
4464: 0e12 push r14
4466: 0f12 push r15
4468: 3012 7d00 push #0x7d
446c: b012 4245 call #0x4542 <INT>
4470: 5f44 fcff mov.b -0x4(r4), r15
4474: 8f11 sxt r15
4476: 3152 add #0x8, sp
4478: 3441 pop r4
447a: 3041 ret
447c .strings:
447c: "Enter the password to continue."
449c: "Remember: passwords are between 8 and 16 characters."
44d1: "Access granted."
44e1: "That password is not correct."
44ff: ""
4500 <login>
4500: 3150 f0ff add #0xfff0, sp
4504: 3f40 7c44 mov #0x447c "Enter the password to continue.", r15
4508: b012 a645 call #0x45a6 <puts>
450c: 3f40 9c44 mov #0x449c "Remember: passwords are between 8 and 16 characters.", r15
4510: b012 a645 call #0x45a6 <puts>
4514: 3e40 3000 mov #0x30, r14
4518: 0f41 mov sp, r15
451a: b012 9645 call #0x4596 <getsn>
451e: 0f41 mov sp, r15
4520: b012 5244 call #0x4452 <test_password_valid>
4524: 0f93 tst r15
4526: 0524 jz #0x4532 <login+0x32>
4528: b012 4644 call #0x4446 <unlock_door>
452c: 3f40 d144 mov #0x44d1 "Access granted.", r15
4530: 023c jmp #0x4536 <login+0x36>
4532: 3f40 e144 mov #0x44e1 "That password is not correct.", r15
4536: b012 a645 call #0x45a6 <puts>
453a: 3150 1000 add #0x10, sp
453e: 3041 ret
4540 <__do_nothing>
4540: 3041 ret
4542 <INT>
4542: 1e41 0200 mov 0x2(sp), r14
4546: 0212 push sr
4548: 0f4e mov r14, r15
454a: 8f10 swpb r15
454c: 024f mov r15, sr
454e: 32d0 0080 bis #0x8000, sr
4552: b012 1000 call #0x10
4556: 3241 pop sr
4558: 3041 ret
455a <putchar>
455a: 2183 decd sp
455c: 0f12 push r15
455e: 0312 push #0x0
4560: 814f 0400 mov r15, 0x4(sp)
4564: b012 4245 call #0x4542 <INT>
4568: 1f41 0400 mov 0x4(sp), r15
456c: 3150 0600 add #0x6, sp
4570: 3041 ret
4572 <getchar>
4572: 0412 push r4
4574: 0441 mov sp, r4
4576: 2453 incd r4
4578: 2183 decd sp
457a: 3f40 fcff mov #0xfffc, r15
457e: 0f54 add r4, r15
4580: 0f12 push r15
4582: 1312 push #0x1
4584: b012 4245 call #0x4542 <INT>
4588: 5f44 fcff mov.b -0x4(r4), r15
458c: 8f11 sxt r15
458e: 3150 0600 add #0x6, sp
4592: 3441 pop r4
4594: 3041 ret
4596 <getsn>
4596: 0e12 push r14
4598: 0f12 push r15
459a: 2312 push #0x2
459c: b012 4245 call #0x4542 <INT>
45a0: 3150 0600 add #0x6, sp
45a4: 3041 ret
45a6 <puts>
45a6: 0b12 push r11
45a8: 0b4f mov r15, r11
45aa: 073c jmp #0x45ba <puts+0x14>
45ac: 1b53 inc r11
45ae: 8f11 sxt r15
45b0: 0f12 push r15
45b2: 0312 push #0x0
45b4: b012 4245 call #0x4542 <INT>
45b8: 2152 add #0x4, sp
45ba: 6f4b mov.b @r11, r15
45bc: 4f93 tst.b r15
45be: f623 jnz #0x45ac <puts+0x6>
45c0: 3012 0a00 push #0xa
45c4: 0312 push #0x0
45c6: b012 4245 call #0x4542 <INT>
45ca: 2152 add #0x4, sp
45cc: 0f43 clr r15
45ce: 3b41 pop r11
45d0: 3041 ret
45d2 <_unexpected_>
45d2: 0013 reti pc

So I’ll admit, this one took me a little longer to figure out. Let’s go through the basic steps I took to see where I got stumped:

main()

4438 <main>
4438:  b012 0045      call	#0x4500 <login>

All main() does is make a call to login(). Let’s follow login()

login()

4500 <login>
4500:  3150 f0ff      add	#0xfff0, sp
4504:  3f40 7c44      mov	#0x447c "Enter the password to continue.", r15
4508:  b012 a645      call	#0x45a6 <puts>
450c:  3f40 9c44      mov	#0x449c "Remember: passwords are between 8 and 16 characters.", r15
4510:  b012 a645      call	#0x45a6 <puts>
4514:  3e40 3000      mov	#0x30, r14
4518:  0f41           mov	sp, r15
451a:  b012 9645      call	#0x4596 <getsn>
451e:  0f41           mov	sp, r15
4520:  b012 5244      call	#0x4452 <test_password_valid>
4524:  0f93           tst	r15
4526:  0524           jz	#0x4532 <login+0x32>
4528:  b012 4644      call	#0x4446 <unlock_door>
452c:  3f40 d144      mov	#0x44d1 "Access granted.", r15
4530:  023c           jmp	#0x4536 <login+0x36>
4532:  3f40 e144      mov	#0x44e1 "That password is not correct.", r15
4536:  b012 a645      call	#0x45a6 <puts>
453a:  3150 1000      add	#0x10, sp
453e:  3041           ret

After a few calls to puts() to print out the password prompt message, the code makes a call to test_password_valid(). Upon returning, login() tests the value of r15 (at 0x4524). If r15 is not 0, we skip the jz instruction at 0x4526 and proceed to unlock_door(). Let’s pay attention to r15 while test_password_valid() is executing…

test_password_valid()

4452 <test_password_valid>
4452:  0412           push	r4
4454:  0441           mov	sp, r4
4456:  2453           incd	r4
4458:  2183           decd	sp
445a:  c443 fcff      mov.b	#0x0, -0x4(r4)
445e:  3e40 fcff      mov	#0xfffc, r14
4462:  0e54           add	r4, r14
4464:  0e12           push	r14
4466:  0f12           push	r15
4468:  3012 7d00      push	#0x7d
446c:  b012 4245      call	#0x4542 <INT>
4470:  5f44 fcff      mov.b	-0x4(r4), r15
4474:  8f11           sxt	r15
4476:  3152           add	#0x8, sp
4478:  3441           pop	r4
447a:  3041           ret

After shifting values inside of various registers/memory, the instruction mov.b -0x4(r4), r15 eventually sets a 0 value into r15. The instruction sxt r15 (sign extend) is executed on r15 (effectively doing nothing since the value is 0x0), and soon returns back into login(), where r15 is tested against 0. It does not appear that we have any influence over the memory address that’s moved into r15 :anguished:.

After racking my brain for a while, and continuously stepping through various parts of program, I noticed something interesting…

Cusco SP PC distance

This version of the code isn’t storing our input at 0x2400 as it was originally, and instead is much closer to the program counter! Notice how close our user input (AAAAAAAAAA) is to pc. I wonder if we can overwrite parts of the program code! Let’s try a bunch of A's and see what happens…

Cusco PC code overwritten

Looks like we can! Another interesting thing to note, I tried to input about 200 or so A's, but only 48 were copied onto the stack. I suspect maybe during the copy onto the stack, __do_copy_data() is being invoked and we’re overwriting instructions as we’re in the middle of executing them? I wasn’t able to step the code to this level of precision so I’m not sure. However, this is sort of a moot point because of what happens as a result of inputting so many bytes…

Cusco PC overwritten

We can control the pc register. Sweet! Now all we need to do is to figure out the byte offset to reach pc and figure out a suitable location to jump the execution to. After some experimenting with input strings of various lengths and keeping an eye on the pc register, we can see that the 17th and 18th byte of our input will overflow into the pc register. Let’s try overwriting pc with 0x4528, the call #0x4446 <unlock_door> instruction inside login() which comes right after the jz instruction we were trying so hard to bypass…

Cusco Solved

:+1: :beers:

Flag (mouse over to reveal)

414141414141414141414141414141412845

jiva

jiva

Security guy, busticati, professional button-pusher

comments powered by Disqus
rss hackthebox keybase facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora