With awk
awk -F '[ \t]+$' 'NF>1{t=substr($0,length($1)+1);gsub(/./,"_",t); $0=$1 t} 1'
This also handles a mixture of trailing tabs and spaces. The field separator (-F, FS) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.
To make that work for leading blanks, everything should be mirrored not just $ to ^:
awk -F '^[ \t]+' 'NF>1{h=substr($0,1,length()-length($2));gsub(/./,"_",h); $0=h $2} 1'
To make it work for both leading and trailing blanks, the logic should be inverted; set the field separator to a pattern not matching leading and trailing blanks:
awk -F '[^ \t](.*[^ \t]|$)' '{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'
Or the same with adjustable pattern:
awk -v ns='[^ \t]' 'BEGIN{FS=ns"(.*"ns"|$)"}{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'
Different from @EdMorton's solutions, these handle correctly lines which contain only spaces and will work with any implementation of awk, not just GNU awk (gawk): eg. with mawk or bwk ("original-awk"), which are both much faster than gawk. But even when used with gawk, the last solution will be almost twice as fast as @EdMorton's.
With sed
With sed, the only solution I can think of is to substitute repeatedly in a loop; if there are many trailing spaces and long lines, this can get slow fast:
sed -e :x -e 's/ \( *\)$/_\1/;tx'
Notice that sed ':x;s/ \( *\)$/_\1/;tx' is not standard sed; :label is not one of the commands which can be terminated by a ;:
Editing commands other than
{...},a,b,c,i,r,t,w,:, and#can be followed by a<semicolon>, optional<blank>characters, and another editing command.
With perl
Here is an alternate perl solution, which is absolutely NOT really an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:
perl -ple 's/\s(?=\s*$)/_/g'