diff options
-rw-r--r-- | src/dlfcn.c | 64 | ||||
-rw-r--r-- | tests/test-dladdr.c | 9 |
2 files changed, 71 insertions, 2 deletions
diff --git a/src/dlfcn.c b/src/dlfcn.c index 3a7ca64..1402df2 100644 --- a/src/dlfcn.c +++ b/src/dlfcn.c | |||
@@ -680,16 +680,54 @@ static BOOL is_valid_address( const void *addr ) | |||
680 | return TRUE; | 680 | return TRUE; |
681 | } | 681 | } |
682 | 682 | ||
683 | #if defined(_M_ARM64) || defined(__aarch64__) | ||
684 | static INT64 sign_extend(UINT64 value, UINT bits) | ||
685 | { | ||
686 | const UINT left = 64 - bits; | ||
687 | const INT64 m1 = -1; | ||
688 | const INT64 wide = (INT64) (value << left); | ||
689 | const INT64 sign = ( wide < 0 ) ? ( m1 << left ) : 0; | ||
690 | |||
691 | return value | sign; | ||
692 | } | ||
693 | #endif | ||
694 | |||
683 | /* Return state if address points to an import thunk | 695 | /* Return state if address points to an import thunk |
684 | * | 696 | * |
685 | * An import thunk is setup with a 'jmp' instruction followed by an | 697 | * On x86, an import thunk is setup with a 'jmp' instruction followed by an |
686 | * absolute address (32bit) or relative offset (64bit) pointing into | 698 | * absolute address (32bit) or relative offset (64bit) pointing into |
687 | * the import address table (iat), which is partially maintained by | 699 | * the import address table (iat), which is partially maintained by |
688 | * the runtime linker. | 700 | * the runtime linker. |
701 | * | ||
702 | * On ARM64, an import thunk is also a relative jump pointing into the | ||
703 | * import address table, implemented by the following three instructions: | ||
704 | * | ||
705 | * adrp x16, [page_offset] | ||
706 | * Calculates the page address (aligned to 4KB) the IAT is at, based | ||
707 | * on the value of x16, with page_offset. | ||
708 | * | ||
709 | * ldr x16, [x16, offset] | ||
710 | * Calculates the final IAT address, x16 <- x16 + offset. | ||
711 | * | ||
712 | * br x16 | ||
713 | * Jump to the address in x16. | ||
714 | * | ||
715 | * The register used here is hardcoded to be x16. | ||
689 | */ | 716 | */ |
690 | static BOOL is_import_thunk( const void *addr ) | 717 | static BOOL is_import_thunk( const void *addr ) |
691 | { | 718 | { |
719 | #if defined(_M_ARM64) || defined(__aarch64__) | ||
720 | ULONG opCode1 = * (ULONG *) ( (BYTE *) addr ); | ||
721 | ULONG opCode2 = * (ULONG *) ( (BYTE *) addr + 4 ); | ||
722 | ULONG opCode3 = * (ULONG *) ( (BYTE *) addr + 8 ); | ||
723 | |||
724 | return (opCode1 & 0x9f00001f) == 0x90000010 /* adrp x16, [page_offset] */ | ||
725 | && (opCode2 & 0xffe003ff) == 0xf9400210 /* ldr x16, [x16, offset] */ | ||
726 | && opCode3 == 0xd61f0200 /* br x16 */ | ||
727 | ? TRUE : FALSE; | ||
728 | #else | ||
692 | return *(short *) addr == 0x25ff ? TRUE : FALSE; | 729 | return *(short *) addr == 0x25ff ? TRUE : FALSE; |
730 | #endif | ||
693 | } | 731 | } |
694 | 732 | ||
695 | /* Return adress from the import address table (iat), | 733 | /* Return adress from the import address table (iat), |
@@ -698,11 +736,32 @@ static BOOL is_import_thunk( const void *addr ) | |||
698 | static void *get_address_from_import_address_table( void *iat, DWORD iat_size, const void *addr ) | 736 | static void *get_address_from_import_address_table( void *iat, DWORD iat_size, const void *addr ) |
699 | { | 737 | { |
700 | BYTE *thkp = (BYTE *) addr; | 738 | BYTE *thkp = (BYTE *) addr; |
739 | #if defined(_M_ARM64) || defined(__aarch64__) | ||
740 | /* | ||
741 | * typical import thunk in ARM64: | ||
742 | * 0x7ff772ae78c0 <+25760>: adrp x16, 1 | ||
743 | * 0x7ff772ae78c4 <+25764>: ldr x16, [x16, #0xdc0] | ||
744 | * 0x7ff772ae78c8 <+25768>: br x16 | ||
745 | */ | ||
746 | ULONG opCode1 = * (ULONG *) ( (BYTE *) addr ); | ||
747 | ULONG opCode2 = * (ULONG *) ( (BYTE *) addr + 4 ); | ||
748 | |||
749 | /* Extract the offset from adrp instruction */ | ||
750 | UINT64 pageLow2 = (opCode1 >> 29) & 3; | ||
751 | UINT64 pageHigh19 = (opCode1 >> 5) & ~(~0ull << 19); | ||
752 | INT64 page = sign_extend((pageHigh19 << 2) | pageLow2, 21) << 12; | ||
753 | |||
754 | /* Extract the offset from ldr instruction */ | ||
755 | UINT64 offset = ((opCode2 >> 10) & ~(~0ull << 12)) << 3; | ||
756 | |||
757 | /* Calculate the final address */ | ||
758 | BYTE *ptr = (BYTE *) ( (ULONG64) thkp & ~0xfffull ) + page + offset; | ||
759 | #else | ||
701 | /* Get offset from thunk table (after instruction 0xff 0x25) | 760 | /* Get offset from thunk table (after instruction 0xff 0x25) |
702 | * 4018c8 <_VirtualQuery>: ff 25 4a 8a 00 00 | 761 | * 4018c8 <_VirtualQuery>: ff 25 4a 8a 00 00 |
703 | */ | 762 | */ |
704 | ULONG offset = *(ULONG *)( thkp + 2 ); | 763 | ULONG offset = *(ULONG *)( thkp + 2 ); |
705 | #ifdef _WIN64 | 764 | #if defined(_M_AMD64) || defined(__x86_64__) |
706 | /* On 64 bit the offset is relative | 765 | /* On 64 bit the offset is relative |
707 | * 4018c8: ff 25 4a 8a 00 00 jmpq *0x8a4a(%rip) # 40a318 <__imp_VirtualQuery> | 766 | * 4018c8: ff 25 4a 8a 00 00 jmpq *0x8a4a(%rip) # 40a318 <__imp_VirtualQuery> |
708 | * And can be also negative (MSVC in WDK) | 767 | * And can be also negative (MSVC in WDK) |
@@ -716,6 +775,7 @@ static void *get_address_from_import_address_table( void *iat, DWORD iat_size, c | |||
716 | */ | 775 | */ |
717 | BYTE *ptr = (BYTE *) offset; | 776 | BYTE *ptr = (BYTE *) offset; |
718 | #endif | 777 | #endif |
778 | #endif | ||
719 | 779 | ||
720 | if( !is_valid_address( ptr ) || ptr < (BYTE *) iat || ptr > (BYTE *) iat + iat_size ) | 780 | if( !is_valid_address( ptr ) || ptr < (BYTE *) iat || ptr > (BYTE *) iat + iat_size ) |
721 | return NULL; | 781 | return NULL; |
diff --git a/tests/test-dladdr.c b/tests/test-dladdr.c index e2f6c01..5568f23 100644 --- a/tests/test-dladdr.c +++ b/tests/test-dladdr.c | |||
@@ -140,12 +140,21 @@ __declspec(dllexport) | |||
140 | #endif | 140 | #endif |
141 | int main(int argc, char **argv) | 141 | int main(int argc, char **argv) |
142 | { | 142 | { |
143 | #if defined(_M_ARM64) || defined(__aarch64__) | ||
144 | /* points to non reachable address */ | ||
145 | unsigned char zero_thunk_address[12] = { 0x10, 0x00, 0x00, 0x90, 0x10, 0x02, 0x40, 0xF9, 0x00, 0x02, 0x1F, 0xD6 }; | ||
146 | /* points to executable base */ | ||
147 | unsigned char invalid_thunk_address[12] = { 0x10, 0x00, 0x00, 0xb0, 0x10, 0x06, 0x47, 0xF9, 0x00, 0x02, 0x1F, 0xD6 }; | ||
148 | /* no import thunk */ | ||
149 | unsigned char no_import_thunk[12] = { 0x11, 0x00, 0x00, 0xb0, 0x31, 0x06, 0x47, 0xF9, 0x20, 0x02, 0x1F, 0xD6 }; | ||
150 | #else | ||
143 | /* points to non reachable address */ | 151 | /* points to non reachable address */ |
144 | unsigned char zero_thunk_address[6] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; | 152 | unsigned char zero_thunk_address[6] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; |
145 | /* points to executable base */ | 153 | /* points to executable base */ |
146 | unsigned char invalid_thunk_address[6] = { 0xFF, 0x25, 0x00, 0x00, 0x40, 0x00 }; | 154 | unsigned char invalid_thunk_address[6] = { 0xFF, 0x25, 0x00, 0x00, 0x40, 0x00 }; |
147 | /* no import thunk */ | 155 | /* no import thunk */ |
148 | unsigned char no_import_thunk[6] = { 0xFF, 0x26, 0x00, 0x00, 0x40, 0x00 }; | 156 | unsigned char no_import_thunk[6] = { 0xFF, 0x26, 0x00, 0x00, 0x40, 0x00 }; |
157 | #endif | ||
149 | int result = 0; | 158 | int result = 0; |
150 | UNUSED(argv); | 159 | UNUSED(argv); |
151 | 160 | ||