I found the following answer here:
The short answer is that the .ko file is your object file linked with
some kernel automatically generated data structures that are needed by
The .o file is the object file of your modules - the result of
compiling your c files. The kernel build system then automatically
creates another C file with some data structures describing the kernel
module (named your_module_kmod.c), compile this C file into another
object file and links your object file and the object file it built
together to create the .ko file.
The dynamic linker in the kernel that is in charge of loading kernel
modules, expects to find the data structure the kernel put in the kmod
object in the .ko file and will not be able to load your kernel module
Also from that source, citing tldp: Up to 2.4 kernel versions, it was ".o", and since 2.6, it's ".ko".