You can actually usually get a pretty good starting point from just a single build, and only refine it once you find a build it breaks on. It's essentially just finding a unique substring. In my experience this almost always involves some wildcard sections, so the signature in the parent got lucky not to need them. I like to think about it as more of matching the shape of the original instructions than matching them verbatim.
To manually construct a signature, you basically just take what the existing instructions encode to, and wildcard out the bits which are likely to change between builds. Then you'll see if it's still a unique match, and if not add a few more instructions on. This will be things like absolute addresses, larger pointer offsets, the length of relative jumps, and sometimes even what registers the instructions operate on. Here's an example of mine that needed all of those:
"48 8B ?? ????????", // mov rcx, [rdi+000001D0]
"48 85 C9", // test rcx, rcx
"74 ??", // je Talos2-Win64-Shipping.exe+25EE729
"E8 ????????", // call Talos2-Win64-Shipping.exe+25E45F0
"48 63 ?? ????????", // movsxd rax, dword ptr [rbx+000005D0]
"8D 70 FF" // lea esi, [rax-01]
Now since making a signature is essentially just finding a unique substring, with a handful of extra rules for wildcards, you can also automate it. Here's a ghidra script (not my own) which I've found quite handy.https://github.com/nosoop/ghidra_scripts/blob/master/makesig...