I have always used the KeAddSystemServiceTable method to locate the shadow table base address but there are plenty of other ways to do it as well, for example traversing threads (i.e> PsLookupThreadByThreadId) and locating a GUI thread. The address is kept in the thread's KTHREAD.ServiceTable field. Might want to validate this address range too in case it's been altered because some malware do this and redirect to a copied shadow table that contains hooked services. You can also obtain the shadow table address easily from usermode, again many ways really.Yes, it is possible to obtain address of the shadow table from GUI thread structure. However, as you said, some malware alter that field, so you need to validate it. I haven't seen any malware modifying KeAddSystemServiceTable routine to confuse those getting the address of the shadow table from there. So, KeAddSystemServiceTable seems to me as safer approach.
And yes, it is possible to obain the address from usermode too. For example, you can load ntoskrnl image to memory and search KeAddSystemServiceTable function.
I also noticed that KeServiceDescriptorTable (which is exported) and KeServiceDescriptorTableShadow are located really near each other (in fact, one table follows the other). I am wondering, whether this holds for all versions of Windows kernel. Didanybody examinte this?