Question: After seeing how the List and Maybe monads are defined, I naturally became curious about how
 the operations `>>=` and `return` are defined for the IO monad.

Answer: 
You will be disappointed, but the `>>=` in `IO` monad isn't that interesting. To quote the GHC source:

```
{- |
A value of type @'IO' a@ is a computation which, when performed,
does some I\/O before returning a value of type @a@.

There is really only one way to \"perform\" an I\/O action: bind it to
@Main.main@ in your program.  When your program is run, the I\/O will
be performed.  It isn't possible to perform I\/O from an arbitrary
function, unless that function is itself in the 'IO' monad and called
at some point, directly or indirectly, from @Main.main@.

'IO' is a monad, so 'IO' actions can be combined using either the do-notation
or the '>>' and '>>=' operations from the 'Monad' class.
-}
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

```

That means `IO` monad is declared as the instance of `State` `State#` monad, and its `>>=` is defined there (and its implementation is fairly easy to guess).

See [IO inside](http://www.haskell.org/haskellwiki/IO_inside#Welcome_to_the_RealWorld.2C_baby_:.29) article on Haskell wiki for more details about the `IO` monad.
It is also helpful to look at the [Haskell docs](http://www.haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html#t:IO), where every position has small "Source" link on the right.

**Update:** And there goes another disappointment, which is my answer, because I didn't notice the '#' in `State#`.
However `IO` behaves like `State` monad carrying abstract `RealWorld` state

As @ehird wrote `State#` is compiler's internal and `>>=` for the `IO` monad is defined in [GHC.Base](https://hackage.haskell.org/package/base-4.14.1.0/docs/src/GHC.Base.html#bindIO) module:

```
instance  Monad IO  where
    {-# INLINE return #-}
    {-# INLINE (>>)   #-}
    {-# INLINE (>>=)  #-}
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s

```
They don't do anything special, and are just there for sequencing actions. It would help if you think of them with different names:

>>= becomes "and then, using the result of the previous action,"

>> becomes "and then,"

return becomes "do nothing, but the result of doing nothing is"

This turns this function:

```
main :: IO ()
main = putStr "hello"
   >>  return " world"
   >>= putStrLn

```

becomes:

```
main :: IO ()
main = putStr "hello" and then,
       do nothing, but the result of doing nothing is " world"
       and then, using the result of the previous action, putStrLn

```

In the end, there's nothing magical about IO. It's exactly as magical as a semicolon is in C.