0

I am writing a utility that needs to test specific memory region.

I need something similar to linux's mmap routine but without linking any libraries. The utility is running on x86-64 linux and has basic C/Assembly routines without any libraries. I've set the entry point with a linker script.

When I try to access memory outside of the original .text, .data, .bss or other original sections, I get a segfault.

I need to make outside memory available to this tool.

Any suggestions would be greatly appreciated!

I've been linking with linker script configurations with MEMORY and SECTIONs, but still no luck.

suncowiam
  • 1
  • 1
  • Have you tried calling the mmap() system call? – fuz Dec 25 '22 at 18:17
  • 1
    You know you are accessing virtual memory, right? So what's the point of using a specific address? Sounds like a XY problem. – Jester Dec 26 '22 at 01:09
  • @fuz, Is there a syscall equivalent of mmap()? Yes, I have used mmap(), but have the constraint of not being able to include system libraries. – suncowiam Dec 26 '22 at 02:21
  • @Jester. Yes, I understand that I'm playing with virtual memory. I do not need access to actual physical memory but do need specific address ranges. This is a very specific tool which I can't discuss outside of this requirement. How do I allocate specific memory ranges from assembly? This can be predefined at link time also if there is a way with linker script. – suncowiam Dec 26 '22 at 02:23
  • 3
    `mmap` *is* a system call. The glibc `mmap(2)` function is just a thin wrapper for it, that's why it's in section 2 of the manual. Given the calling convention and a call number from `unistd_64.h`, you can use `syscall` manually to invoke any system call, just check the manual for kernel vs. libc differences (e.g. for brk or nice; there aren't any for x86-64 mmap). – Peter Cordes Dec 26 '22 at 02:31
  • 1
    You should be able to set the address of an arbitrary section in the linker script. You say you have tried that but get a segfault? Make sure your section is writable. Also, provide [mcve]. Without a linker script it's as simple as `.section fixed, "aw"` then `--section-start=fixed=0x42420000` – Jester Dec 26 '22 at 11:57
  • There aren't any *differences* between the kernel API and the documented library API. Unlike for example `getpriority(2)`. Always check the NOTES section of the man page, e.g. https://man7.org/linux/man-pages/man2/getpriority.2.html#NOTES. For `mmap`, https://man7.org/linux/man-pages/man2/mmap.2.html#NOTES says "Since kernel 2.4, that system call has been superseded by mmap2(2), and nowadays the glibc mmap() wrapper function invokes mmap2(2) with a suitably adjusted value for offset." but that only applies to older architectures like i386; x86-64 mmap Just Works. – Peter Cordes Dec 26 '22 at 18:23
  • If 64-bit addresses aren't working, maybe you're truncating a pointer to 32-bit somewhere. `mmap(hint, MAP_FIXED)` definitely works with any canonical address. (Or better MAP_FIXED_NOREPLACE). Oh, maybe you're trying 64-bit addresses are outside the low 47 bits, so they're not canonical. (The Linux kernel reserves the high half for its own use.) Check the mmap return value (using `strace ./a.out` if you don't want to write extra code.) [x86-64 canonical address?](https://stackoverflow.com/q/25852367) – Peter Cordes Dec 26 '22 at 18:27
  • See also [Why in x86-64 the virtual address are 4 bits shorter than physical (48 bits vs. 52 long)?](https://stackoverflow.com/q/46509152) / [Why do x86-64 systems have only a 48 bit virtual address space?](https://stackoverflow.com/q/6716946) . (On CPUs with the PML5 feature, your kernel can use it to allow 57-bit virtual addresses, so user-space can use the low 56 bits.) – Peter Cordes Dec 26 '22 at 18:28
  • Thanks all, I got the syscall mmap working. Apparently, I tried to allocate a region that was too big but did not return an error code. After allocating smaller chunks to fit my external regions, it started to work beyond 32 bit addressing. My cases involved many fragmented regions of memory in and out of 32 bit addressing. I tried an entire mapping from start to end of all my separate regions, which did not initially work. Thanks! – suncowiam Dec 27 '22 at 16:41

1 Answers1

0

If you want full access to all of the memory you need to boot in your app. I think you'll find that rather complicated.

One tool that does such is the memory testing app. under Linux. It's called memtest86+.

This requires you to start in 8086, and if you want access to all the RAM and have more than a 32 bit CPU can access, you need to switch to amd64 (64 bits CPU). Then you can check the entire memory.

The BIOS does that to detect all the memory banks and inform you about those (i.e. you have ways to get that information when your boot code starts allowing you to load your "OS"/code in a memory buffer that is available and large enough).

Under a modern OS (Windows 2k and newer, OS/X and newer, modern Unices such as Linux), the memory is managed using the kernel and all you see is virtual memory. This means the pointers you get in your apps under such a system have nothing to do with real memory pointers as you get on a boot. To do that, the CPU uses a unit called the MMU (or Memory Management Unit). In the old days, that was a separate processor. Now it's all in one.


Basic memory access is done using any instruction that allow for access to the memory. To load a value, you use the MOV instruction:

MOV register, [address]
MOV register, [register]

In the first instruction, you use a fixed address.

In the second instruction, you loaded a register with an address and then load that value. That second solution is probably what you are looking for. You can then increase the register with an ADD and access the next location. Something like this:

    MOV r1, 123456    // load address
    MOV r2, 256       // number of MOV to do
loop:
    MOV r3, [r1]
    ...do something with r3?...
    ADD r1, 4         // 4 for 32 bits, 8 for 64 bits, etc.
    SUB r2, 1
    JE loop

Here the r2 register is used to define how many reads to do.

In the old days, the Unix/Windows processes always started at a specific address, so you could can actually hard coded the 123456 address. Newer systems use ASLR which means the address changes on each restart of your application. That being said, you can capture the RIP register to get the current .text pointer:

    LEA rax, [rip]

With older processors (not 64 bits), you probably would need to use a CALL:

    CALL to_pop
to_pop:
    POP eax

Since you don't really clearly define what memory range you want to read, I can't really help much more here, but the basic principal shown above should be sufficient to help you with your endeavor.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • Thanks so much for your answer. I should clarify that I just need to access a specific memory range and not actual physical memory. As long as my assembly code can exercise the specific memory ranges, then that will suffice. Again, something like linux mmap() will do but I cannot include system libraries. Would there be a syscall? Or can this be done in the linker script as I do know the specific memory ranges at link time? Thanks – suncowiam Dec 26 '22 at 02:25
  • *Since a Unix/Windows process start at a specific address* - Not true for modern PIE executables on Linux (although you can of course build traditional ELF_TYPE = EXEC non-PIE executables. Also not true for Windows, where ASLR of static text/data is also normal, unless you disable it. – Peter Cordes Dec 27 '22 at 04:06
  • It looks like [ASLR of the .text segment](https://security.stackexchange.com/questions/70569/aslr-does-not-seem-to-randomize-text-section) is a _recent_ change in Linux. I was not aware that had changed. – Alexis Wilke Dec 27 '22 at 05:38
  • Yeah, on by default in most distros starting around 2017. [32-bit absolute addresses no longer allowed in x86-64 Linux?](https://stackoverflow.com/q/43367427) Somewhat recent, but not *recent*; that's almost 6 years ago. – Peter Cordes Dec 27 '22 at 06:04