Microcorruption CTF New Orleans Write-up
- 3 minsSummary:
This is a write-up of my solution to the Microcorruption CTF challenge “New Orleans” (LOCKIT PRO r a.01
).
Let’s begin by first taking a look inside the main function:
4438 <main> | |
4438: 3150 9cff add #0xff9c, sp | |
443c: b012 7e44 call #0x447e <create_password> | |
4440: 3f40 e444 mov #0x44e4 "Enter the password to continue", r15 | |
4444: b012 9445 call #0x4594 <puts> | |
4448: 0f41 mov sp, r15 | |
444a: b012 b244 call #0x44b2 <get_password> | |
444e: 0f41 mov sp, r15 | |
4450: b012 bc44 call #0x44bc <check_password> | |
4454: 0f93 tst r15 | |
4456: 0520 jnz #0x4462 <main+0x2a> | |
4458: 3f40 0345 mov #0x4503 "Invalid password; try again.", r15 | |
445c: b012 9445 call #0x4594 <puts> | |
4460: 063c jmp #0x446e <main+0x36> | |
4462: 3f40 2045 mov #0x4520 "Access Granted!", r15 | |
4466: b012 9445 call #0x4594 <puts> | |
446a: b012 d644 call #0x44d6 <unlock_door> | |
446e: 0f43 clr r15 | |
4470: 3150 6400 add #0x64, sp |
A few things look interesting. First, a call to a function called create_password()
, then eventually a call to a function called check_password()
. We can see that after the call to check_password()
, r15
is compared with zero. If r15
is not zero, execution will jump to 0x4462
, which will print the access granted message and will call the unlock_door()
routine. First, let’s see what create_password()
is doing:
447e <create_password> | |
447e: 3f40 0024 mov #0x2400, r15 | |
4482: ff40 4200 0000 mov.b #0x42, 0x0(r15) | |
4488: ff40 3c00 0100 mov.b #0x3c, 0x1(r15) | |
448e: ff40 5b00 0200 mov.b #0x5b, 0x2(r15) | |
4494: ff40 4600 0300 mov.b #0x46, 0x3(r15) | |
449a: ff40 5a00 0400 mov.b #0x5a, 0x4(r15) | |
44a0: ff40 4000 0500 mov.b #0x40, 0x5(r15) | |
44a6: ff40 4100 0600 mov.b #0x41, 0x6(r15) | |
44ac: cf43 0700 mov.b #0x0, 0x7(r15) | |
44b0: 3041 |
We can see that 8 bytes are being loaded into the memory location at 0x2400
. Cool. Let’s keep this in mind and check out what’s going on inside check_password()
:
44bc <check_password> | |
44bc: 0e43 clr r14 | |
44be: 0d4f mov r15, r13 | |
44c0: 0d5e add r14, r13 | |
44c2: ee9d 0024 cmp.b @r13, 0x2400(r14) | |
44c6: 0520 jne #0x44d2 <check_password+0x16> | |
44c8: 1e53 inc r14 | |
44ca: 3e92 cmp #0x8, r14 | |
44cc: f823 jne #0x44be <check_password+0x2> | |
44ce: 1f43 mov #0x1, r15 | |
44d0: 3041 ret | |
44d2: 0f43 clr r15 | |
44d4: 3041 ret |
We can see that the instruction cmp.b @r13, 0x2400(r14)
will compare the memory location pointed to from r13
(the location of the first byte of our input) with the byte located at memory address 0x2400
(the location of the memory containing 8 bytes set by the previous call to create_password()
). These instructions will then loop, comparing each byte from memory with a byte from the input. If the comparison does not succeed, the following instruction jne #0x44d2 <check_password+0x16>
will jump the execution to 0x44d2
, which will clear the r15
register and return to main()
. Because r15
contains 0
at this point, the subsequent tst
instruction (in main()
) will fail and execution will not reach unlock_door()
. However, if the byte comparisons succeed, 0x1
will be loaded into r15
, and execution will return to main()
at which point the tst
instruction will succeed. Let’s input the bytes that we noted from the call to create_password()
and see what happens…
Flag (mouse over to reveal)
B<[FZ@A