TL;DR - It might actually be as simple as db.shards.updateOne({_id: 'shard1'}, {$set: {host: 'shard1/s1r1:27017, s1r2:27017, s1r3:27017'}). Still can’t find anything in the docs to support this and I certainly wouldn’t recommend doing it live (especially with the balancer running).
Hate to respond to my own question, but I’ll say what we did in case it helps anyone else who needs to rebind a sharded cluster to a new set of IPs.
First we ensured that all members in the cluster had an IP + hostname entry in /etc/hosts with the current set of IPs. We also updated all config files to use bindIpAll: true. Then we stopped the balancer and shutdown the cluster except for the config replica set. We used db.shards.updateOne() on the config db to change the existing IPs in the host value to use hostnames. Finally we used rs.reconfig() to update the config replica set to use hostnames.
We restarted each shard and used rs.reconfig() to use hostnames. Before restarting the query routers we updated the config files to specify the config replica set by hostnames instead of IPs for this field:
sharding:
configDB: <configReplSetName>/cfg1.example.net:27019, cfg2.example.net:27019,...
Once the QRs were up, the cluster was live on the existing IP addresses, but in an IP agnostic state. Every member was using a hostname to refer to any other member of the cluster. Changing the IPs was now a simple matter of updating the networking interface on the VM and the /etc/hosts IP => hostname mapping (or using DNS and removing the hardcoded mapping in /etc/hosts). Our cluster is live again on a new network. If there really isn’t more to it than changing the host values for each of the shard documents in the config.shards collection I’m confused as to why there isn’t an sh.reconfig() method.