Hello folks ! Here is a write up for the two first pwn challenges of the ASIS CTF.
You can find the related files here.
justpwnit
justpwnit was a warmup pwn challenge. That’s only a basic stack overflow.
The binary is statically linked and here is the checksec’s output:
1 | [*] '/home/nasm/justpwnit' |
Morever the source code is provided as it is the case for all the pwn tasks !
Here it is:
1 | /* |
The program is basically reading STR_SIZE
bytes into parray[index]
, the issue is that there is no check on the user controlled index from which we choose were write the input.
Furthermore, index
is a signed integer, which means we can input a negative value. If we do so we will be able to overwrite the saved $rbp
value of the set_element
stackframe by a heap pointer to our input. By this way at the end of the pwninit, the leave
instruction will pivot the stack from the original state to a pointer to the user input.
Let’s see this in gdb !
1 | 00:0000│ rsp 0x7ffef03864e0 ◂— 0x0 |
That’s the stack’s state when we are calling calloc. We can see the set_element
‘s stackframe which ends up in $rsp+38
with the saved return address. And right after we see that $rax
contains the address of the parray
buffer. Which means that if we send -2 as index, $rbp
will point to the newly allocated buffer to which we will write right after with fgets
.
Then, if we do so, the stack’s state looks like this:
1 | 00:0000│ rsp 0x7ffef03864e0 ◂— 0x0 |
The saved $rbp
has been overwritten with a pointer to the user input. Then, at the end of the set_element
function, $rbp
is popped from the stack and contains a pointer to the user input. Which causes at the end of the justpwnit
function, the leave
instruction moves the pointer to the user input in $rsp
.
ROPchain
Once we can pivot the stack to makes it point to some user controlled areas, we just have to rop through all the gadgets we can find in the binary.
The binary is statically linked, and there is no system function in the binary, so we can’t make a ret2system, we have to make a execve("/bin/sh\0", NULL, NULL)
.
And so what we need is:
- pop rdi gadget
- pop rsi gadget
- pop rdx gadget
- pop rax gadget
- syscall gadget
- mov qword ptr [reg], reg [to write “/bin/sh\0”] in a writable area
We can easily find these gadgets with the help ROPgadget.
We got:
1 | 0x0000000000406c32 : mov qword ptr [rax], rsi ; ret |
Now we just have to craft the ropchain !
1 | POP_RDI = 0x0000000000401b0d |
And we can enjoy the shell !
1 | ➜ justpwnit git:(master) ✗ python3 exploit.py HOST=168.119.108.148 PORT=11010 |
Full exploit
1 | #!/usr/bin/env python |
abbr
abbr is very basic heap overflow, we just have to overwrite a function pointer to a stack pivot gadget with the help of a user controlled register. Then, we can drop a shell with a similar ROP as for the justpwnit
challenge (the binary is also statically linked without the system function).
Here is the source code:
1 |
|
The rules.h
looks like this:
1 | typedef struct { |
The main stuff is in english_expand
function which is looking for an abreviation in the user input. If it finds the abbreviation, all the data after the occurence will be written further according to the length of the full expression.
The attack idea is fairly simple, the text
variable is allocated right before the Translator
structure, and so in the heap they will be contiguous. Given that, we know that if we send 0x1000 bytes in the chunk contained by text
and that we put an abbreviation of the right length we can overwrite the translate
function pointer.
I will not describe in details how we can find the right size for the abbreviation or the length off the necessary padding.
An interesting abbreviation is the www
, which stands for “write what where” (what a nice abbreviation for a pwner lmao), indeed the expanded expression has a length of 16 bytes.
So we send b"wwwwww" + b"A"*(0x1000-16) + pwn.p64(gadget)
, we will overflow the 32 first bytes next the text
chunk, and in this rewrite the translator
function pointer.
ROPchain
Once that’s done, when the function pointer will be triggered at the next iteration, we will be able to jmp at an arbitrary location.
Lets take a look at the values of the registers when we trigger the function pointer:
1 | RAX 0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop rdi |
$rax
points to the newly readen input, same for $r8
and $rdi
and $rdx
contains the location to which we will jmp on.
So we can search gadgets like mov rsp, rax
, mov rsp, rdi
, mov rsp, r8
and so on. But I didn’t find any gadgets like that, so I looked for xchg rsp
gadgets, and I finally found a xchg eax, esp
gadgets ! Since the binary is not PIE based, the heap addresses fit into a 32 bits register, so that’s perfect!
Now we can make $rsp
to point to the user input, we make a similar ropchain as the last challenge, and that’s enough to get a shell!
1 |
|
We launch the script with the right arguments and we correctly pop a shell!
1 | ➜ abbr.d git:(master) ✗ python3 exploit.py HOST=168.119.108.148 PORT=10010 |
Final exploit
1 | #!/usr/bin/env python |