Hackerl/pangolin

problems on freebsd

hellomarks opened this issue · 17 comments

Hello, i test "pangolin" success on ubuntu, but when test on freebsd, it fails, there is no file "/proc/*/auxv", how to bypass? thanks!

If there is no /proc/*/auxv, you may need to obtain it manually and then splice it into a array.
https://github.com/Hackerl/elf-loader/blob/anonymous/src/loader.c#L47

static size_t read_auxv(Elf_auxv_t *auxv, size_t size) {
    int fd = open(AV_PATH, O_RDONLY, 0);

    if (fd < 0) {
        int n = 0;

        for (int i = 0; i < AT_MAX; i++) {
            if (n >= size)
                return -1;

            unsigned long val = getauxval(i);

            if (val == 0)
                continue;

            auxv[n / sizeof(Elf_auxv_t)].a_type = i;
            auxv[n / sizeof(Elf_auxv_t)].a_un.a_val = val;

            n += sizeof(Elf_auxv_t);
        }

        return n;
    }

    ssize_t n = read(fd, auxv, size);

    if (n < 0) {
        close(fd);
        return 0;
    }

    close(fd);
    return n;
}

I forgot, the injected code cannot call any libc api.

i finally bypass it , input values manually as follows from ddexec.sh, but on freebsd, there‘s more than this.
# Auxiliary vector
at_random=$(endian $(printf %016x $at_random))
local auxv=""
auxv=$auxv"0300000000000000"$1 # phaddr
auxv=$auxv"0400000000000000"$2 # phentsize
auxv=$auxv"0500000000000000"$3 # phnum
if [ -n "$4" ]
then
auxv=$auxv"0700000000000000"$(endian $4) # ld_base
fi
auxv=$auxv"0900000000000000"$5 # entry
auxv=$auxv"1900000000000000"$at_random # AT_RANDOM
auxv=$auxv"0600000000000000""0010000000000000" # AT_PAGESZ
auxv=$auxv"0000000000000000""0000000000000000" # AT_NULL
auxv=$auxv"aaaaaaaaaaaaaaaa""bbbbbbbbbbbbbbbb" # Should be two random values
stack=$stack$auxv$args"0000000000000000" # NULL at the end of the stack

ld.so uses auxiliary vector for shared library loading and symbol relocation. I have not tried manually setting only AT_PHDR, AT_PHENT, AT_PHNUM, AT_BASE, AT_ENTRY, AT_EXECFN, will these work normally?
I simply tested it:

LD_SHOW_AUXV=1 sleep 1000
AT_SYSINFO_EHDR: 0x7ffff7028000
AT_HWCAP:        f8bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x5609d6dba040
AT_PHENT:        56
AT_PHNUM:        11
AT_BASE:         0x7fdc8d621000
AT_FLAGS:        0x0
AT_ENTRY:        0x5609d6dbbb30
AT_UID:          0
AT_EUID:         0
AT_GID:          0
AT_EGID:         0
AT_SECURE:       0
AT_RANDOM:       0x7ffff6ff94c9
AT_HWCAP2:       0x0
AT_EXECFN:       /usr/bin/sleep
AT_PLATFORM:     x86_64

It seems that we can obtain most of the fields in other ways and then set them manually, for example AT_SYSINFO_EHDR can look for the [vdso] keyword in /proc/pid/maps.
Then splice unsigned long type key and value to manually construct an auxiliary vector.

Thanks! In freebsd, 'procstat auxv pid' can tell us the auxv values, but I can not load elf on freebsd so far , do you have any ideas?

Freebsd seems to obtain auxv through syscall, the injected code can call syscall, it should be possible to modify the shellcode for compatibility.
https://github.com/mjoras/freebsd/blob/master/lib/libprocstat/libprocstat.c#L2418

static Elf_Auxinfo *
procstat_getauxv_sysctl(pid_t pid, unsigned int *cntp)
{
	Elf_Auxinfo *auxv;
	int name[4];
	size_t len;

#if __ELF_WORD_SIZE == 64
	if (is_elf32_sysctl(pid))
		return (procstat_getauxv32_sysctl(pid, cntp));
#endif
	name[0] = CTL_KERN;
	name[1] = KERN_PROC;
	name[2] = KERN_PROC_AUXV;
	name[3] = pid;
	len = PROC_AUXV_MAX * sizeof(Elf_Auxinfo);
	auxv = malloc(len);
	if (auxv == NULL) {
		warn("malloc(%zu)", len);
		return (NULL);
	}
	if (sysctl(name, nitems(name), auxv, &len, NULL, 0) == -1) {
		if (errno != ESRCH && errno != EPERM)
			warn("sysctl: kern.proc.auxv: %d: %d", pid, errno);
		free(auxv);
		return (NULL);
	}
	*cntp = len / sizeof(Elf_Auxinfo);
	return (auxv);
}

I will find time to complete this feature.

Hello, I just tried it on freebsd and found that it is very different from linux. Some syscalls are not available on it, not even /proc/pid. If you want to make compatibility, it is almost equivalent to rewriting a new project. I'm afraid I don't have that much time.
If you are interested in trying to be compatible with freebsd, you can fork a new project and discuss any implementation issues together.

FreeBSD should be similar to macos. I have developed on macos, there is no proc file system, so you need to use libproc or sysctl to obtain process information. Then make the shellcode of syscall compatible, if the API of ptrace are the same, it should work.

thank you. I have modified all necessary syscalls and successfully compiled the “shellcode” sub folder, injecting the shellcode into aother process using other tool. However, during debugging, there were no problems with loading segments from elf itself and the lib library, and printf in the shellcode is ok too, and I have already run to the last step "calling entry" , and it fails then. I am not sure what's the problem now?
I use 'procstat auxv pid' to get the real values and input values mannualy before inject.

When the elf entry is executed on Linux, the data on the stack are argc, argv, envp, auvx, but I am not sure whether the same is true for freebsd. You may have to look at the source code of the freebsd elf loader part.
Also, debugging shellcode is too difficult, try starting with a standalone elf loader.
My shellcode is borrowed from https://github.com/MikhailProg/elf, which is an elf loader that can run independently and is very friendly for debugging. You might as well try to modify this project first so that it can work normally on freebsd.
If it works, then the shellcode must work, and you can move on.

Freebsd's stack seems to be arranged in this way, https://github.com/lattera/freebsd/blob/master/libexec/rtld-elf/rtld.c#L351.
Can you tell me about your final failure?

image
I modified https://github.com/MikhailProg/elf, and then it crashed after running. I noticed that there were an unknown 24 bytes more data on the stack at entry, and argc came after 24 bytes.
Maybe we can solve this problem just by figuring out what the extra 24 bytes are.

diff --git a/src/loader.c b/src/loader.c
index 1c11a61..1a52925 100644
--- a/src/loader.c
+++ b/src/loader.c
@@ -173,7 +173,6 @@ void z_entry(unsigned long *sp, void (*fini)(void))
                AVSET(AT_PHNUM, av, ehdrs[Z_PROG].e_phnum);
                AVSET(AT_PHENT, av, ehdrs[Z_PROG].e_phentsize);
                AVSET(AT_ENTRY, av, entry[Z_PROG]);
-               AVSET(AT_EXECFN, av, (unsigned long)argv[1]);
                AVSET(AT_BASE, av, elf_interp ?
                                base[Z_INTERP] : av->a_un.a_val);
                }
diff --git a/src/z_elf.h b/src/z_elf.h
index b360150..a5da99f 100644
--- a/src/z_elf.h
+++ b/src/z_elf.h
@@ -3,6 +3,30 @@

 #include <elf.h>

+typedef struct
+{
+  uint32_t a_type;              /* Entry type */
+  union
+    {
+      uint32_t a_val;           /* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+         though, since it does not work when using 32-bit definitions
+         on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf32_auxv_t;
+
+typedef struct
+{
+  uint64_t a_type;              /* Entry type */
+  union
+    {
+      uint64_t a_val;           /* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+         though, since it does not work when using 32-bit definitions
+         on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf64_auxv_t;
+
 #if ELFCLASS == ELFCLASS64
 #  define Elf_Ehdr     Elf64_Ehdr
 #  define Elf_Phdr     Elf64_Phdr
diff --git a/src/z_syscalls.c b/src/z_syscalls.c
index ab873ab..ed4e4c4 100644
--- a/src/z_syscalls.c
+++ b/src/z_syscalls.c
@@ -1,4 +1,4 @@
-#include <syscall.h>
+#include <sys/syscall.h>

 #include "z_asm.h"
 #include "z_syscalls.h"
diff --git a/src/z_utils.h b/src/z_utils.h
index 1f75781..5a91ee9 100644
--- a/src/z_utils.h
+++ b/src/z_utils.h
@@ -3,7 +3,6 @@

 #include <stdlib.h>
 #include <stdarg.h>
-#include <alloca.h>
 #include <string.h>

 #define z_alloca       __builtin_alloca

Be sure to use gmake DEBUG=1 to compile.

OK! From https://github.com/malisal/loaders/blob/master/elf/elf.c#L319 it tells something about bsd,but problems are the same. got "Segmentation fault"

image
I succeeded. It turns out that freebsd will place the top of the stack pointer in rdi when calling entry, instead of using the current rsp.
https://github.com/lattera/freebsd/blob/master/libexec/rtld-elf/amd64/rtld_start.S#L32
https://github.com/lattera/freebsd/blob/master/libexec/rtld-elf/rtld.c#L314

diff --git a/src/Makefile b/src/Makefile
index 3b3ba90..5158e82 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -8,7 +8,7 @@ ARCHS := $(ARCHS32) $(ARCHS64)

 CFLAGS += -pipe -Wall -Wextra -fPIC -fno-ident -fno-stack-protector -U _FORTIFY_SOURCE
 LDFLAGS += -nostartfiles -nodefaultlibs -nostdlib
-LDFLAGS += -pie -e z_start -Wl,-Bsymbolic,--no-undefined,--build-id=none
+LDFLAGS += -static -pie -e z_start -Wl,-Bsymbolic,--no-undefined,--build-id=none
 TARGET := loader

 ifeq "$(filter $(ARCH),$(ARCHS))" ""
diff --git a/src/amd64/z_trampo.S b/src/amd64/z_trampo.S
index 8e3881d..de75c3b 100644
--- a/src/amd64/z_trampo.S
+++ b/src/amd64/z_trampo.S
@@ -4,7 +4,9 @@
        .type   z_trampo,@function
 z_trampo:
        mov     %rsi,   %rsp
-       jmp     *%rdi
+        mov     %rdi,   %rax;
+        mov     %rsp,   %rdi;
+       jmp     *%rax;
        /* Should not reach. */
        hlt

diff --git a/src/loader.c b/src/loader.c
index 1c11a61..1a52925 100644
--- a/src/loader.c
+++ b/src/loader.c
@@ -173,7 +173,6 @@ void z_entry(unsigned long *sp, void (*fini)(void))
                AVSET(AT_PHNUM, av, ehdrs[Z_PROG].e_phnum);
                AVSET(AT_PHENT, av, ehdrs[Z_PROG].e_phentsize);
                AVSET(AT_ENTRY, av, entry[Z_PROG]);
-               AVSET(AT_EXECFN, av, (unsigned long)argv[1]);
                AVSET(AT_BASE, av, elf_interp ?
                                base[Z_INTERP] : av->a_un.a_val);
                }
diff --git a/src/z_elf.h b/src/z_elf.h
index b360150..a5da99f 100644
--- a/src/z_elf.h
+++ b/src/z_elf.h
@@ -3,6 +3,30 @@

 #include <elf.h>

+typedef struct
+{
+  uint32_t a_type;              /* Entry type */
+  union
+    {
+      uint32_t a_val;           /* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+         though, since it does not work when using 32-bit definitions
+         on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf32_auxv_t;
+
+typedef struct
+{
+  uint64_t a_type;              /* Entry type */
+  union
+    {
+      uint64_t a_val;           /* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+         though, since it does not work when using 32-bit definitions
+         on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf64_auxv_t;
+
 #if ELFCLASS == ELFCLASS64
 #  define Elf_Ehdr     Elf64_Ehdr
 #  define Elf_Phdr     Elf64_Phdr
diff --git a/src/z_syscalls.c b/src/z_syscalls.c
index ab873ab..ed4e4c4 100644
--- a/src/z_syscalls.c
+++ b/src/z_syscalls.c
@@ -1,4 +1,4 @@
-#include <syscall.h>
+#include <sys/syscall.h>

 #include "z_asm.h"
 #include "z_syscalls.h"
diff --git a/src/z_utils.h b/src/z_utils.h
index 1f75781..5a91ee9 100644
--- a/src/z_utils.h
+++ b/src/z_utils.h
@@ -3,7 +3,6 @@

 #include <stdlib.h>
 #include <stdarg.h>
-#include <alloca.h>
 #include <string.h>

 #define z_alloca       __builtin_alloca

Great ! Thank you very much!!! I get success too.