The best way is using an ezbl project as base then change it to your needs. Ezbl bootloader is not trivial but it is the most secure way.
from https://www.microchip.com/SWLibraryWeb/product.aspx?product=Microchip%20Easy%20Bootloader
EZBL Features
Targets supported
• Single and Dual Partition bootloading topology examples on Dual Partition capable targets
• Common code set supports all 32-bit PIC32MM and 16-bit PIC24/dsPIC33 devices, excluding PIC24F[V]xxKxx families
• Dual Partition with Live Update for time and state sensitive application continuity
Code reuse
• Transparent Bootloader function, ISR and variable access from executing Application
• Existing code can be integrated into a Bootloader or Application project without significant changes
• File-oriented push/pull and onboard-memory bootloaders ready to use - can be adapted to Over The Air (OTA) and chip-to-chip designs
Automatic linker script creation
• No .gld/.ld file maintenance or understanding of GNU ld syntax required
Decoupled communications
• 2-wire UART and I2C Slave protocols exampled
• USB Mass Storage thumb drive bootloading (USB Host/MSD Class)
• Passive, self-recognizing protocol scalable to multi-node broadcast oriented communications buses
• Multiple peripheral instances and peripheral types can simultaneously listen for firmware updates
• Partitioned to facilitate customer and 3rd party protocol additions
Robust self-preservation
• Flash erase/write routines self-aware of Bootloader geometry
• Bootloader will not erase or corrupt itself when externally given destructive commands
• Will not attempt to execute a corrupt or partially bootloaded Application
• CRC32 communications and image integrity checking with reusable API
Interrupt vector management
• IVT entries forwarded to optimized Interrupt Goto Table in Application space
• Bootloader and Application can share any Interrupt Vector with individual run-time selection
• No AIVT or Boot Segment (BS) hardware support required
Application support functionality
• Optimized general purpose 64-bit time measurement and task scheduling API
• Flash Erase/Write APIs for data EEPROM emulation and content survival independent of application upgrades
• Multi-instance FIFO buffering API with interrupt/non-blocking, polled, timeout and callback notification functions
Designed for performance without hardware luxuries
• Latency adaptive software flow control tolerates Bluetooth or TCP/Internet propagation delays without hardware RTS/CTS or sideband signaling
• .hex contents automatically converted at build time to smaller binary .bl2 image with improved identification, version and error detection features
• Software FIFOs on Bootloader communications interrupts permit high throughput
Code secrecy compatible
• Operation unaffected by ICSP Code Protect
• Bootloader does not expose program memory or RAM for external read back
• External program verification/version identification facilitated by internal CRC32 computation
Full source with no-cost Microchip license
• No GNU GPL code contamination or 3rd party code requiring independent licensing
• Script-accessible, portable C PC command line communications executable ready for branded GUI wrapping and redistribution