1

I have the following x86 assembly command from gdb:

mov eax, gs:0x14

When I type (gdb) info registers the value for gs turns out to be 0x63. From what I read, to get to the address itself I have to multiply gs by 0x10 and add the offset (0x14).

As expected, the address cannot be read from memory, because this is a relative address. I tried to objdump the file to try to find any meaningful start point to which I should add 0x644 to get to the real memory address, but nothing popped up. When I run the file in gdb, the addresses are always 0x056555XXX, but adding 0x644 to 0x56555000 lands right in the middle of the code.

Where is this memory segment actually located?

edit: I'm running this on a 64-bit kali linux VM, but the file is from some CTF and is 32-bit i386 elf file. I don't know if it's protected or real mode...

Yonatan H
  • 43
  • 1
  • 9
  • gs is a register. how all this works depends on the mode you are running this in, where, what processor (not just x86 but a 32 bit a 64 bit, etc), is this a simulation of a 386, etc, etc. then there is virtual address and physical address which your code nor files will see. which leads to operating system on top of all the rest of it as to how they dole out memory, possibly even the toolchain and its linker script. please edit the question. – old_timer Sep 07 '20 at 22:14
  • Since you mentioned `eax` and `gdb` I assume you are in 32 bit protected mode in linux. The calculation you used applies to real mode segmentation only. You need to query the `gs` segment base value from the OS. – Jester Sep 07 '20 at 22:16
  • 3
    The computation you described is only for real mode. Assuming you’re in protected mode, you have to get the value of GS Base some other way. In 32-bit mode, the processor gets the segment base address from the GDT. (Also, in no case is it a relative address.) – prl Sep 07 '20 at 22:19
  • 2
    It is protected mode. – Jester Sep 07 '20 at 22:23
  • 1
    If your program has been linked with `libpthread` then the gdb command `info threads` will show the TLS base address as the thread id, e.g. `* 1 Thread 0xf7dc3700 (LWP 24305) ` has address `0xf7dc3700`. This is the easiest way to get the address although it's not guaranteed to work. – Jester Sep 07 '20 at 23:15

1 Answers1

3

In protected mode, a segment register basically just contains an index into a descriptor table, and not the actual address of the segment. The descriptor table will contain the base address and size of the segment. The actual breakdown is 13 bits of index, 1 bit local/global selector, and 2 bits of permission level.

In your case, the gs value 0x63 breaks down as a permission level of 3 (bottom two bits), which is actually the lowest (user) permission level; a 0 bit selecting a global descriptor, and an index of 12 in the GDT (global descriptor table). Unfortunately there's no easy way to read the GDT from gdb, but the actual address used for this instruction will be computed from the base address at index 12 in the GDT, with an offset of 0x14 added.

In long (64-bit) mode, the value in the segment register is ignored completely -- instead the base address comes from the GSBASE register. In gdb, you can examine that by using the register name $gs_base:

(gdb) p /x $gs_base

Annoyingly, this is not displayed by info registers (or even info registers all), so you have to know to print it specifically.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 2
    Note that the GS base *won't* usually match what was in the GDT. The OS will have used `wrmsr` to modify the GS base directly for that thread, instead of creating an LDT entry for each different GS base for every thread in the system. See [Detail about MSR\_GS\_BASE in linux x86 64](https://stackoverflow.com/q/11497563) for example. Actual traditional GDT / LDT segmentation mechanisms are so clunky that modern x86 provides better methods for setting GS or FS base for thread-local storage. – Peter Cordes Sep 08 '20 at 18:32
  • @PeterCordes: That is the case in long (64-bit) mode, but not in protected (32-bit) mode. – Chris Dodd Sep 08 '20 at 18:59
  • 32-bit user-space under a 64-bit kernel is compat mode, a sub-mode of long mode. https://en.wikipedia.org/wiki/X86-64#Operating_modes. As per the OP's edit "*I'm running this on a 64-bit kali linux VM*". Also, are you *sure* 32-bit kernels don't use `wrmsr` to `MSR_GS_BASE`? I think it works, and is more efficient than futzing with an LDT and reloading the segment reg. Or were you talking about the specific case of that question I linked, where the kernel used a null selector value for fs and gs? Right, a pure 32-bit kernel would leave some non-zero segment selector. – Peter Cordes Sep 08 '20 at 19:15
  • Since MSR_GS_BASE does not exist on pre-64 bit CPUs, a 32-bit kernel that tried to use it would not run on such a machine, but I guess it might be possible. When running on a 64-bit kernel, every time I've looked the segment registers show up as 0 to gdb, so seeing a non-zero value there would indicate something odd is going on (and a value like 0x63 looks like a legitimate value). I added a note about inspecting $gs_base directly in gdb if the OP is running in a situation where that is possible. Not sure if that works in compat mode, however. – Chris Dodd Sep 08 '20 at 19:27
  • I had been guessing that Linux would use a non-null GS for 32-bit user-space for some backwards-compat reason even though it still set gs base separately. But you're right, my Linux 5.7 kernel runs 32-bit user-space with FS=GS=0. (in a static executable, IDK about with TLS). I would have guessed that modern 32-bit Linux kernels would check for the availability of MSRs on boot, and use that for setting GS base. But they might still have a non-zero GS to get the other parts of the segment descriptor loaded? IDK, maybe the OP has a 32-bit VM after all. – Peter Cordes Sep 08 '20 at 19:37
  • Also, your answer still mis-states some things. The value in the segment register isn't ignored. If you mov a bad value, it still faults. `0` is accepted as a special null value. You *could* update the segment base with a `mov gs, eax`. IIRC the GDT entry isn't wide enough to cover the full 64-bit address range, but setting it that way still sets the same architectural state as wrmsr `MSR_GS_BASE` or `wrgsbase`; the MSR and the actual segment base are truly tied together or are literally the same thing. (That's my understanding at least; possibly *I* have some of it wrong.) – Peter Cordes Sep 08 '20 at 19:46
  • What gdb version has that variable? Mine doesn't seem to (Debian 7.12-6) Ahha, apparently supported from version 8. That certainly makes life easier. – Jester Sep 08 '20 at 23:21