I am not aware of a way to check if a thread is alertable in real-time from usermode. Anyhow, if you read what nullptr said this is the closest you can come I believe. Check the ThreadState and WaitReason members. If the ThreadState is Waiting (5) then this thread would be of interest to possibly queue an APC to. WaitReason will reveal the reason for the wait. An example of this might be DelayExecution (4), the thread might be currently processing a call to Sleep/SleepEx.
In that example however, the numbers will still be the same (State: 5 WaitReason: 4) for the thread but SleepEx with its 2nd arg equal to True means it would be alertable whereas Sleep() would never be. It wouldn't hurt to queue the same APC to any threads you enumerate currently in a wait state or even all existing threads in the process, since a thread who is not currently waiting may enter an alertable wait state later. I noticed however that you mention not having access to writing to the target's address space (NtWriteVirtualMemory), how do you expect to execute custom APC code within that process then? A different method perhaps such as ZwMapViewOfSection etc? Please elaborate on this.
You can look at WaitReasons here
Code: Select alltypedef enum _KWAIT_REASON { Executive, FreePage, PageIn, PoolAllocation, DelayExecution, Suspended, UserRequest, WrExecutive, WrFreePage, WrPageIn, WrPoolAllocation, WrDelayExecution, WrSuspended, WrUserRequest, WrEventPair, WrQueue, WrLpcReceive, WrLpcReply, WrVirtualMemory, WrPageOut, WrRendezvous, Spare2, Spare3, Spare4, Spare5, Spare6, WrKernel, MaximumWaitReason } KWAIT_REASON;