I found a solution to the problem, the IPP .net SDK does let you specify the IteratorId. It turns out the Item property on the CustomerQuery/QueryBase represents the IteratorId XML field. If you don't specify the Item/IteratorId, then calling ExecuteQuery will always return the first 500 results.
Working code sample below:
var cq = new CustomerQuery() { ActiveOnly = true };
// this fills in the IteratorId that is documented on the IPP website
// if you leave this out, the loop below will run infinitely if there
// are >= 500 records returned.
cq.Item = Guid.NewGuid().ToString("N");
ReadOnlyCollection<Ipp.Customer> cqr = null;
do
{
cqr = cq.ExecuteQuery<Ipp.Customer>(context);
// do something with the results returned here.
}
while (cqr.Count == 500);