The attacker has pairs of hashes and known passwords (for instance, for their own accounts) and only has to figure out what the mapping is for those; the answer then extends trivially to the rest of the passwords.
That's the "theoretical" answer. The real answer is, popping someone's database virtually always --- you know what, let's just assume always for now --- gives you a remote shell, and from the the actual code.
Ah, that theoretical answer does fill in one missing link for me. I kept wondering how you could detect even a trivial modification of a standard hashing algorithm (say, reversing the hash) if all you had to look at was its outputs. It seemed like a massive search problem through the space of all possible modifications, though no doubt there exist sophisticated techniques to apply. But if you have some input/output pairs, the problem seems entirely more tractable. Even I can begin to imagine how one might tackle that.
The point you and others make that if the database is compromised then the code almost certainly is too, is clearly the best answer though. I am now prepared for the next time I encounter my interlocutor at extended family dinners!
In my house we have a rule that we don't discuss password hashing at the dinner table
Cryptographically, you can address the problem you've set out for yourself simply by hashing a 128 bit random number along with the password, and keeping that number a secret. If attackers can get your code, it doesn't matter how you obscure your hash, because they'll have the algorithm. But through trial and error, an attacker might figure out how you tweaked an algorithm; all the atoms in the solar system (or something like that, I can never remember) could be computers trialing and erroring against a 128 bit random number and they'd never figure it out.
That's the "theoretical" answer. The real answer is, popping someone's database virtually always --- you know what, let's just assume always for now --- gives you a remote shell, and from the the actual code.