That ^C handling is done by the terminal device driver, not the shell.
It's when that character is received on the wire that connects to a tty device (or written on the master side of the master side of a pseudo-terminal pair (like
xterm does when you press Ctrl+C)) that the tty line discipline (a driver in the kernel) sends the SIGINT signal to the foreground process group of the terminal.
So you'd need to send that character on the fd that xterm has open on the master side.
$ eval "$(xprop -notype -id $WINDOWID 32i '=$0\n' _NET_WM_PID)"; lsof -ap "$_NET_WM_PID" /dev/ptmx
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
xterm 21123 stephane 4u CHR 5,2 0t0 1460 /dev/ptmx
Writing it to
/proc/21123/fd/4 on Linux would not work as that would just reopen
/dev/ptmx so not refer to the same pseudo-terminal. You'd need to convince that
xterm to write that ^C to its fd 4.
So not really an option.
You could start your own pseudo-terminal pair and run sh and ping` in it. Then you could write ^C on the master side for that SIGINT to be sent to the foreground process group, but easier here would be to send a signal directly (SIGTERM would be prefered).
Also note that trying to write a character on
/proc/$pid/fd/0, doesn't really make sense. The file descriptor is usually open for reading. On Linux, doing an open() on that
/proc/$pid/fd/0 actually reopens the resource that that fd points to.
So for instance if
sh was started with
sh < /some/file, doing
echo something > "/proc/$shpid/fd/0" would replace the content of
something\n. If it was a tty, then that would just display
something on that terminal. The only case where writing
something would have that something read by
sh would be when that sh stding is a pipe. On Linux (and Linux only), if you do an
open() in write mode to
/proc/$shpid/fd/0 (where that is a pipe), then that opens the writing end of that pipe (while that fd 0 actually points to the reading end).