How are hooks implemented? Are hooks a thing in their own right, or are they actually implemented as events under the hood?
Hooks aren't Symfony events. They are PHP functions with a specific name that Drupal calls in very specific cases.

For example, hook_cron() is invoked when Drupal cron tasks are executed, from Cron::invokeCronHandlers(), which uses the following code.

  // Iterate through the modules calling their cron handlers (if any):
  foreach ($this-&gt;moduleHandler
    -&gt;getImplementations('cron') as $module) {
    if (!$module_previous) {
      $logger
        -&gt;notice('Starting execution of @module_cron().', [
        '@module' =&gt; $module,
      ]);
    }
    else {
      $logger
        -&gt;notice('Starting execution of @module_cron(), execution of @module_previous_cron() took @time.', [
        '@module' =&gt; $module,
        '@module_previous' =&gt; $module_previous,
        '@time' =&gt; Timer::read('cron_' . $module_previous) . 'ms',
      ]);
    }
    Timer::start('cron_' . $module);

    // Do not let an exception thrown by one module disturb another.
    try {
      $this-&gt;moduleHandler
        -&gt;invoke($module, 'cron');
    } catch (\Exception $e) {
      watchdog_exception('cron', $e);
    }
    Timer::stop('cron_' . $module);
    $module_previous = $module;
  }


A module whose machine name is spam_checker could implement this hook adding the following function to its code.

function spam_checker_cron() {
  // Delete the nodes from the list of nodes to check after 96 hours are passed.
  Drupal::database()
    -&gt;delete('spam_checker_list')
    -&gt;condition('timestamp', REQUEST_TIME - 3600 * 96, '&lt;')
    -&gt;execute();
}