inspecting an executable using ifunc
- How to work with ifunc (with handson examples)
- How to do a quick inspection using CLI commands
- How to deep dive using IDA
Go read
- https://maskray.me/blog/2021-01-18-gnu-indirect-function
- Great resource, has been updated after the xz backdoor incident ๐
- https://a-kawashiro.hatenablog.com/entry/2021/11/07/100540
- Dives into the source code of GCC and ld ๐
/* example.c */
#include <stdio.h>
void foo_real() { printf("%p\t->\t%s\n", foo_real, __func__); }
void foo() __attribute__((ifunc("resolve_foo")));
static void *resolve_foo(void) {
// Return the function address for `foo`
printf("%p\t->\t%s\n", foo_real, __func__);
return foo_real;
}
int main() {
printf("%p\t->\t%s\n", main, __func__);
foo(); // Calls the resolved function
return 0;
}gcc example.c -o example
0x57c0f109d169 -> resolve_foo
0x57c0f109d1d5 -> main
0x57c0f109d169 -> foo_real
Comparison with constructor
/* example.c */
#include <stdio.h>
void foo_real() { printf("%p\t->\t%s\n", foo_real, __func__); }
void foo() __attribute__((ifunc("resolve_foo")));
static void *resolve_foo(void) {
printf("%p\t->\t%s\n", __func__, __func__);
return foo_real;
}
__attribute__((constructor))
void aloha() {
printf("%p\t->\t%s\n", __func__, __func__);
}
int main() {
printf("%p\t->\t%s\n", main, "main");
foo(); // Calls the resolved function
return 0;
}
/* output:
0x5d040a9f5028 -> resolve_foo
0x5d040a9f5034 -> aloha
0x5d040a9f4208 -> main
0x5d040a9f4169 -> foo_real
*/-
readelfreadelf -s <executable>Example:
Observe that 69th entry (foo) is a IFUNC entry mapped to address00000000000011e0. The resolver function (resolve_foo) is also mapped to the same address.Symbol table '.symtab' contains 78 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND ... 34: 0000000000000000 0 SECTION LOCAL DEFAULT 34 .debug_ranges 35: 0000000000000000 0 FILE LOCAL DEFAULT ABS example.c 36: 0000000000002028 9 OBJECT LOCAL DEFAULT 18 __func__.2512 37: 00000000000011e0 53 FUNC LOCAL DEFAULT 16 resolve_foo 38: 0000000000002018 12 OBJECT LOCAL DEFAULT 18 __func__.2517 39: 0000000000002010 5 OBJECT LOCAL DEFAULT 18 __func__.2520 40: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c ... 61: 0000000000001298 0 FUNC GLOBAL HIDDEN 17 _fini 62: 00000000000011b0 37 FUNC GLOBAL DEFAULT 16 foo_real 63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...] 64: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start 65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 66: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle 67: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used 68: 0000000000001220 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init 69: 00000000000011e0 53 IFUNC GLOBAL DEFAULT 16 foo 70: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end 71: 00000000000010c0 47 FUNC GLOBAL DEFAULT 16 _start 72: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 73: 0000000000001080 55 FUNC GLOBAL DEFAULT 16 main ...The
resolve_foogets called when loaded without any direct flow. -
nmnm <binary file>Same idea. Now look for 'i' instead of 'IFUNC'.
- Navigate to the ifunc resolver.
- Note the possible return address value.
- The rest is the same.