Disclaimers: I am a Bitcoin Verde developer, not an ABC developer. I know C++, but I am not completely familiar with ABC's codebase, its flow, and its nuances. Therefore, my explanation may not be completely correct. This explanation is an attempt to inform those that are at least semi- tech-savvy, so the upgrade hiccup does not become a scary boogyman that people don't understand.
1- When a new transaction is received by a node, it is added to the mempool (which is a collection of valid transactions that should/could be included in the next block).
2- During acceptance into the mempool, the number of "sigOps" is counted, which is the number of times a signature validation check is performed (technically, it's not a 1-to-1 count, but its purpose is the same).
2a- The reason behind limiting sigops is because signature verification is usually the most expensive operation to perform while ensuring a transaction is valid. Without limiting the number of sigops a single block can contain, an easy DOS (denial of service) attack can be constructed by creating a block that takes a very long to validate due to it containing transactions that require a disproportionately large number of sigops. Blocks that take too long to validate (i.e. ones with far too many sigops) can cause a lot of problems, including causing blocks to be slowly propagated--which disrupts user experience and can give the incumbent miner a non-negligible competitive advantage to mine the next block. Overall, slow-validating blocks are bad.
3- When accepted to the mempool, the transaction is recorded along with its number of sigops.
3a- This is where the ABC bug lived. During the acceptance of the mempool, the transaction's scripts are parsed and each occurrence of a sigop is counted. When OP_CHECKDATASIG was introduced during the November upgrade, the procedure that counted the number of sigops needed to know if it should count OP_CHECKDATASIG as a sigop or as nothing (since before November, it was not a signature checking operation). The way the procedure knows what to count is controlled by a "flag" that is passed along with the script. If the flag is included, OP_CHECKDATASIG is counted as a sigop; without it, it is counted as nothing. Last November, every place that counted sigops included the flag EXCEPT the place where they were recorded in the mempool--instead, the flag was omitted and transactions using OP_CHECKDATASIG were logged to the mempool as having no sigops.
4- When mining a block, the node creates a candidate block--this prototype is completely valid except for the nonce (and the extended nonce/coinbase). The act of mining is finding the correct nonce. When creating the prototype block, the node queries the mempool and finds transactions that can fit in the next block. One of the criteria used when determining applicability is the sigops count, since a block is only allowed to have a certain number of sigops.
4a- Recall the ABC bug described in step 3a. The number of sigops for transactions using OP_CHECKDATASIG is recorded as zero--but only during the mempool step, not during any of the other operations. So these OP_CHECKDATASIG transactions can all get grouped up into the same block. The prototype block builder thinks the block should have very few sigops, but the actual block has many, many, sigops.
5- When the miner module is ready to begin mining, it requests the prototype block the in step 4. It re-validates the block to ensure it has the correct rules. However, since the new block has too many sigops included in it, the mining software starts working on an empty block (which is not ideal, but more profitable than leaving thousands of ASICs idle doing nothing).
6- The empty block is mined and transmitted to the network. It is a valid block, but does not contain any other transactions other than the coinbase. Again, this is because the prototype block failed to validate due to having too many sigops.
This scenario could have happened at any time after OP_CHECKDATASIG was introduced. By creating many transactions that only use OP_CHECKDATASIG, and then spending them all at the same time would create blocks containing what the mempool thought was very few sigops, but everywhere else contained far too many sigops. Instead of mining an invalid block, the mining software decides to mine an empty block. This is also why the testnet did not discover this bug: the scenario encountered was fabricated by creating a large number of a specifically tailored transactions using OP_CHECKDATASIG, and then spending them all in a 10 minute timespan. This kind of behavior is not something developers (including myself) premeditated.
I hope my understanding is correct. Please, any of ABC devs correct me if I've explained the scenario wrong.