unicorn-engine/unicorn

[question] I need help with GDT

Closed this issue · 4 comments

Hi,

I am trying to do something similar like usercorn in rust: Emulate elf binaries.

The main issue I have is described in lunixbochs/usercorn#267 but since then I got a little bit further:

  1. I load the static linked binary x86_32 into the process
  2. I create an empty GDT table (not setting any segment registers)
  3. I run my binary (simple "printf Hello World")
  4. Durin the setup, it calls set_thread_area
  5. I setup the segment for only GS (and write to the GS register)
  6. On the next access to the stack (ret instruction) I get a READ_UNMAPPED with stack pointer fff0dd2c BUT the address that the unicorn callback reports is 0xdd2c (missing the upper bytes).

I did not yet figure out a minimal example that triggers it and before I throw a huge amount of code at you I wanted to ask you the following:

  1. Do you know why setting the GS register may cause the access to the stack to fail?
  2. Do you know whether I can set only the GS register? Or do I also have to set up all the other registers for gs:offset to be readable?

PS:

  • When I set the segment registers to weird values, I get a Segfault (emulation crashed, maybe I hit #550)
  • I am using the latest version of unicorn

ping @sashs, as I found your blog describing how to set gdt up with unicorn engine

I created a 'minimal' example by modifying the gdt example:
https://gist.github.com/felberj/9a8170c47ee4648995d7bab568707baf

$ ./gdt_demo
Executing at 0x1000000, ilen = 0x1
mem access: address: 0x100 esp 0x120100

So, I recently solved this issue myself and have a tentative pull request to Usercorn to help solve it there as well: lunixbochs/usercorn#268.

What you're seeing is when you don't set the GDTR, the stack access seems to work normally, but as soon as you set a GDT entry, now you're getting truncated memory values. I don't know all the technical details, but I'm assuming that once the GDTR is set, the emulator assumes that a valid GDT is present and tries to use stack access via the SS entry. You need to set an appropriate entry for SS with the proper base/limit on stack access.

Here are some quick examples:

gdt_entries[1] = create_gdt_entry(0x0, 0xfffff000, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_0 | A_DIR_CON_BIT, F_PROT_32);

gdt_entries[2] = create_gdt_entry(0x0, 0xfffff000, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_0, F_PROT_32);

gdt_entries[3] = create_gdt_entry(fs_base, 0xfffff000, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32);

gdt_entries[4] = create_gdt_entry(gs_base, 0xfffff000, A_PRESENT | A_DATA | A_DATA_WRITABLE | A_PRIV_3 | A_DIR_CON_BIT, F_PROT_32);

Where entry 1 is for CS/DS/ES, entry 2 for SS, entry 3 for FS, and entry 4 for GS. Then when creating the actual descriptor values:

auto selector = create_selector(1, S_GDT | S_PRIV_0);
uc_reg_write(unicorn_engine, UC_X86_REG_CS, &selector);
uc_reg_write(unicorn_engine, UC_X86_REG_DS, &selector);
uc_reg_write(unicorn_engine, UC_X86_REG_ES, &selector);

selector = create_selector(2, S_GDT | S_PRIV_0);
uc_reg_write(unicorn_engine, UC_X86_REG_SS, &selector);
        
selector = create_selector(3, S_GDT | S_PRIV_3);
uc_reg_write(unicorn_engine, UC_X86_REG_FS, &selector);

selector = create_selector(4, S_GDT | S_PRIV_3);
uc_reg_write(unicorn_engine, UC_X86_REG_GS, &selector);

I'm using the create_selector and create_gdt_entry functions found is @sashs blog.

I may be able to post a more detailed example later.

Thank you very much!