The answer to my question is that sub was not looking in the right place, so to speak! This works:
$ printf "a ab c d b" | awk '{for (i=1;i<=NF;i++) if ($i=="b") sub("b","X",$i); print}'
a ab c d X
The third sub argument is the target for replacement.