[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:
- I load the static linked binary x86_32 into the process
- I create an empty GDT table (not setting any segment registers)
- I run my binary (simple "printf Hello World")
- Durin the setup, it calls set_thread_area
- I setup the segment for only GS (and write to the GS register)
- On the next access to the stack (
ret
instruction) I get aREAD_UNMAPPED
with stack pointerfff0dd2c
BUT the address that the unicorn callback reports is0xdd2c
(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:
- Do you know why setting the GS register may cause the access to the stack to fail?
- 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!