zyantific / zydis

Fast and lightweight x86/x86-64 disassembler and code generation library
https://zydis.re
MIT License
3.47k stars 436 forks source link

I want to relocate a piece of code to new memory #403

Closed injertao closed 1 year ago

injertao commented 2 years ago

Dumped.exe+FFFF5A - E8 E132BA01 - call Dumped.exe+2BA3240 ==>00620000 - E8 3B320234 - call Dumped.exe+2BA3240 E8 E132BA01 == > E8 3B320234

I hope it can overload the in the new runtime-address And calculate relative addressing Please tell me which example to refer to Thanks.

oberrich commented 2 years ago

https://github.com/zyantific/zydis/blob/807af6873edea49addcd3e3a0a7ea3c9fbc8bef3/tools/ZydisTestEncoderAbsolute.c#L128

You could also calculate the absolute address and relocate it manually

// pseudocode
get_abs(addr_instrn, offset_to_disp)
  return addr_instrn + *(int32_t*)(addr_instrn + offset_to_disp) + sizeof(int32_t) + offset_to_disp;

With this knowledge relocating it should be a piece of cake.

injertao commented 2 years ago

@oberrich Thank you for your answer, but it doesn't seem to be what I want. E8 E132BA01 == > E8 3B320234 I want to get 3B320234 through E132BA01

oberrich commented 2 years ago

You have to calculate the absolute address and make it relative to its new location. The numbers after the E8 opcode are rip-relative displacements, see https://www.felixcloutier.com/x86/call.html

injertao commented 2 years ago

@oberrich Yes, I understand that an absolute address is required, but when it is in a new runtime address, how should I obtain the relative code

injertao commented 2 years ago

I want to transfer "call Dumped. exe+2BA3240" from "Dumped. exe+FFFF5A" to the new address "0x00620000". What shall I do? thanks

injertao commented 2 years ago

When I use ZydisEncoderEncodeInstruction, how do I make runtime-address work? Can you see what I mean?

oberrich commented 2 years ago

I haven't used the encoder myself but from taking a quick look at it maybe a combination of ZydisEncoderDecodedInstructionToEncoderRequest and ZydisEncoderEncodeInstruction could work. With the information I have provided before it's trivial to do even without the encoder.

You can also just calculate the absolute address and rewrite the call to use an absolute address like so:

movabs rax, 0xdeadbeef
call rax

or even something like this if you dont know which register is safe to use:

call [addr_data]
jmp addr_data_end
addr_data: 0xdeadbeef
addr_data_end:
...
injertao commented 2 years ago

Thank you. It seems that this is the only way. This is a problem that has existed before. I think the 4.0 update can solve it

mappzor commented 2 years ago

Seems like you are looking for ZydisEncoderEncodeInstructionAbsolute. It accepts absolute addresses and converts them to RIP-relative ones: https://doc.zydis.re/v4.0.0/html/group__encoder#ga8c631aa30e550914d953ca7e1dea4db8

injertao commented 2 years ago

@mappzor It doesn't seem to work as expected Old Code and runtime-address: 210E848A - 8B 05 DC4A9D01 - mov eax,[22ABCF6C] New Code and runtime-address: 1DE12636B3A - A1 6CCFAB2200000000 - mov eax,[22ABCF6C]

ZydisEncoderEncodeInstructionAbsolute(&req, encoded_instruction, &encoded_length, 0x1DE12636B3A) return ZYAN_STATUS_INVALID_ARGUMENT

mappzor commented 2 years ago

Can you show how encoder request gets constructed? Did you initialize encoded_length correctly?

injertao commented 2 years ago

@mappzor

    // Initialize decoder context 
    ZydisDecoder decoder;
    ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);

    // Initialize formatter. Only required when you actually plan to do instruction 
    // formatting ("disassembling"), like we do here 
    ZydisFormatter formatter;
    ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);

    // Loop over the instructions in our buffer. 
    // The runtime-address (instruction pointer) is chosen arbitrary here in order to better 
    // visualize relative addressing 
  ZyanUSize offset = 0x10;
    const ZyanUSize length = sizeof(code4);
    ZydisDecodedInstruction instruction;
    ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];

    ULONG_PTR newRuntimeAddress = (ULONG_PTR)newData;
    while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code4 + offset, length - offset,
        &instruction, operands)))
    {
        {
            ZydisEncoderRequest req;
            ExpectSuccess(ZydisEncoderDecodedInstructionToEncoderRequest(&instruction, operands,
                instruction.operand_count_visible, &req));

            ZyanU8 encoded_instruction[ZYDIS_MAX_INSTRUCTION_LENGTH];
            ZyanUSize encoded_length = sizeof(encoded_instruction);
          if (ZYAN_FAILED(ZydisEncoderEncodeInstructionAbsolute(&req, encoded_instruction, &encoded_length, ULONG_PTR(code4 + offset))))
            {
                puts("123");
            }
            newRuntimeAddress += encoded_length;
        }
        offset += instruction.length;
    }
injertao commented 2 years ago

@mappzor It has changed from 6 bytes to 9 bytes. How can I predict the size and data of 9 bytes

mappzor commented 2 years ago

I'm a bit confused. Earlier you've mentioned failed encoding with ZYAN_STATUS_INVALID_ARGUMENT. Now you mention changed instruction size (which is perfectly fine). So which one is the case here?

injertao commented 2 years ago

@mappzor Sorry, my English is not very good. In fact, I just want to decode and write to a new runtime-address and calculate the new byte size

injertao commented 2 years ago
210E848A - 8B 05 DC4A9D01 - mov eax,[22ABCF6C]
1DE12636B3A - A1 6CCFAB2200000000 - mov eax,[22ABCF6C]

I want to calculate the byte 8B 05 DC4A9D01 of 210E848A into A1 6CCFAB2200000000 and write it into 1DE12636B3A, so that the program can operate normally

mappzor commented 2 years ago

Algorithm looks as follows:

  1. Decode instruction
  2. Call ZydisEncoderDecodedInstructionToEncoderRequest
  3. Encoder request contains raw immediates/displacements, so you should convert them to absolute addresses before calling ZydisEncoderEncodeInstructionAbsolute.
    • for ZYDIS_OPERAND_TYPE_IMMEDIATE check if decoded operand has is_relative => use ZydisCalcAbsoluteAddress to convert to absolute
    • for ZYDIS_OPERAND_TYPE_MEMORY check if base address is RIP/EIP => use ZydisCalcAbsoluteAddress to convert to absolute
  4. Call ZydisEncoderEncodeInstructionAbsolute
mappzor commented 2 years ago

ZydisEncoderEncodeInstruction encodes immediates/displacements as supplied inside encoder request without any modifications:

memset(&req, 0, sizeof(req));
req.machine_mode = ZYDIS_MACHINE_MODE_LONG_64;
req.mnemonic = ZYDIS_MNEMONIC_JMP;
req.operand_count = 1;
req.operands[0].type = ZYDIS_OPERAND_TYPE_IMMEDIATE;
req.operands[0].imm.u = 0x11223344;
// Produces: E9 44 33 22 11

ZydisEncoderEncodeInstructionAbsolute expects absolute addresses in place of immediates/displacements, so 0x11223344 gets interpreted as desired destination for the jump. For example:

injertao commented 2 years ago

@mappzor I still can't seem to get what I need

I don't even know the difference between ZydisEncoderEncodeInstructionAbsoluteand ZydisEncoderEncodeInstruction

I just want to get 9 bytes of data based on the original 6 bytes

For example:

210E848A - 8B 05 DC4A9D01 - mov eax,[22ABCF6C]

1DE12636B3A - A1 6CCFAB2200000000 - mov eax,[22ABCF6C]

8B 05 DC4A9D01 = > A1 6CCFAB2200000000 Sorry, I may be stupid, or you don't fully understand what I mean

injertao commented 2 years ago

@mappzor

              operands[i].encoding = ZYDIS_OPERAND_ENCODING_JIMM32;

              instruction.raw.imm->size = 32;
              instruction.raw.imm->offset = 2;

              instruction.meta.branch_type = ZYDIS_BRANCH_TYPE_NEAR;

              operands[i].size = 32;
              operands[i].element_size = 32;

              operands[i].encoding = ZYDIS_OPERAND_ENCODING_DISP16_32_64;
              operands[i].mem.base = ZYDIS_REGISTER_NONE;

I found that these parameters need to be changed before using ZydisEncoderEncodeInstructionAbsolute, but whether there is a function that can intelligently change it, long jump or short jump, relative or absolute

mappzor commented 2 years ago

Please avoid deleting and heavy editing existing comments. This makes discussion really hard to follow.

I don't even know the difference between ZydisEncoderEncodeInstructionAbsolute and ZydisEncoderEncodeInstruction

See my previous comment for detailed explanation. It's useful for relocating rip-relative memory operands and jumps in a generic manner.

8B 05 DC4A9D01 = > A1 6CCFAB2200000000

If you want to force movabs you need to set base register to none (instead of RIP) and displacement to absolute address (use ZydisCalcAbsoluteAddress). That's a special case you will have to handle manually. Other instructions cannot be handled in the same way.

I found that these parameters need to be changed before using ZydisEncoderEncodeInstructionAbsolute, but whether there is a function that can intelligently change it, long jump or short jump, relative or absolute

Set branch_type to ZYDIS_BRANCH_TYPE_NONE. See documentation for details: https://doc.zydis.re/v4.0.0/html/structZydisEncoderRequest__#a0fc13a5a26ccb4d406021418c65b5556

injertao commented 2 years ago

This is the target assembly 210E848A - 8B 05 DC4A9D01 - mov eax,[22ABCF6C] After I modify the parameters as follows:

if (operands[i].type == ZYDIS_OPERAND_TYPE_MEMORY) { ZyanU64 absolute_address = NULL; ZydisCalcAbsoluteAddress(&instruction, &operands[i],0x210E848A, &absolute_address); operands[i].mem.disp.value = absolute_address; operands[i].mem.base = ZYDIS_REGISTERNONE; } I got it. It seems that everything is normal, but `encoded Lengthis equal to 6, not 10 1CEE6570000 - 67 A1 6CCFAB2200000000 - mov eax,[22ABCF6C]`

injertao commented 2 years ago

@mappzor

                if (IsIMM)
                {
                    operands[i].encoding = ZYDIS_OPERAND_ENCODING_JIMM64;

                    instruction.raw.imm->size = 64;
                    instruction.raw.imm->offset = 4;

                    instruction.meta.branch_type = ZYDIS_BRANCH_TYPE_FAR;

                    operands[i].size = 64;
                    operands[i].element_size = 64;
                }

Whether there are parameters similar to ZYDIS_BRANCH_TYPE_NONEin encoding

mappzor commented 2 years ago

Don't change anything in decoded instruction and operands. You are supposed to decode -> ZydisEncoderDecodedInstructionToEncoderRequest -> customize encoder request -> encode.

injertao commented 2 years ago

@mappzor

        ZyanUSize offset = 0x10;
        const ZyanUSize length = sizeof(code4);
        ZydisDecodedInstruction instruction;
        ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];

        ULONG_PTR newRuntimeAddress = (ULONG_PTR)newData;
        while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code4 + offset, length - offset,
            &instruction, operands)))
        {
            {

                for (size_t i = 0; i < instruction.operand_count_visible; i++)
                {
                    if (operands[i].type == ZYDIS_OPERAND_TYPE_MEMORY) 
                    {
                        ZyanU64 absolute_address = NULL;
                        ZydisCalcAbsoluteAddress(&instruction, &operands[i],0x210E848A, &absolute_address);
                        operands[i].mem.disp.value = absolute_address;
                        operands[i].mem.base = ZYDIS_REGISTER_NONE;
                    }
                    else if (operands[i].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[i].imm.is_relative)
                    {
                        ZyanU64 absolute_address = NULL;
                        ZydisCalcAbsoluteAddress(&instruction, &operands[i], newRuntimeAddress, &absolute_address);
                        operands[i].imm.value.s = absolute_address;
                    }
                }

                ZydisEncoderRequest req;
                ExpectSuccess(ZydisEncoderDecodedInstructionToEncoderRequest(&instruction, operands,
                    instruction.operand_count_visible, &req));
                req.branch_type = ZYDIS_BRANCH_TYPE_NONE;
                ZyanU8 encoded_instruction[ZYDIS_MAX_INSTRUCTION_LENGTH];
                ZyanUSize encoded_length = sizeof(encoded_instruction);
                if (ZYAN_FAILED(ZydisEncoderEncodeInstructionAbsolute(&req, encoded_instruction, &encoded_length, 0x210E848A)))
                {
                    puts("123");
                }
                newRuntimeAddress += encoded_length;
            }
            offset += instruction.length;
        }
    }

Before ZydisEncoderDecodedInstructionToEncoderRequest, I only modified operators. Is that right? By analogy, before ZydisEncoderEncodeInstructionAbsolute, I should only modify req, right? If I understand correctly, then the encoded_lengthI get is still 6 instead of 10,

But I found an interesting thing,

operands[i].mem.disp.value = absolute_ address;

I'm here absolute_Address+=0x100000000, you can correctly get the code I want, encoded_Length is 9

It seems that in the ZydisEncoderEncodeInstructionAbsolute, the length of the calculation instruction is based on the size of operands [i]. mem.disp.value. Sometimes, though the value is less than 0xFFFFFFFF, I still want to get an 8-byte absolute address code

mappzor commented 2 years ago

Before ZydisEncoderDecodedInstructionToEncoderRequest, I only modified operators. Is that right?

No, don't do this. Apply all modifications to encoder request ONLY (req.operands).

It seems that in the ZydisEncoderEncodeInstructionAbsolute, the length of the calculation instruction is based on the size of operands [i]. mem.disp.value. Sometimes, though the value is less than 0xFFFFFFFF, I still want to get an 8-byte absolute address code

If you want to change rip-relative movs to movabs you need to do that manually (see my previous comment). Encoder will never do that for you automatically.

injertao commented 2 years ago

@mappzor No, don't do this. Apply all modifications to encoder request ONLY (req.operands).

        while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, code4 + offset, length - offset,
            &instruction, operands)))
        {
                ZydisEncoderRequest req;
                ExpectSuccess(ZydisEncoderDecodedInstructionToEncoderRequest(&instruction, operands,
                    instruction.operand_count_visible, &req));
                req.branch_type = ZYDIS_BRANCH_TYPE_NONE;
                for (size_t i = 0; i < req.operand_count; i++)
                {
                    if (req.operands[i].type == ZYDIS_OPERAND_TYPE_MEMORY)
                    {
                        ZyanU64 absolute_address = NULL;
                        ZydisCalcAbsoluteAddress(&instruction, &operands[i], 0x210E848A, &absolute_address);
                        req.operands[i].mem.displacement = absolute_address;
                        req.operands[i].mem.base = ZYDIS_REGISTER_NONE;
                    }
                    else if (operands[i].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && operands[i].imm.is_relative)
                    {
                        ZyanU64 absolute_address = NULL;
                        ZydisCalcAbsoluteAddress(&instruction, &operands[i], newRuntimeAddress, &absolute_address);
                        req.operands[i].imm.s = absolute_address;
                    }
                }
                ZyanU8 encoded_instruction[ZYDIS_MAX_INSTRUCTION_LENGTH];
                ZyanUSize encoded_length = sizeof(encoded_instruction);
                if (ZYAN_FAILED(ZydisEncoderEncodeInstructionAbsolute(&req, encoded_instruction, &encoded_length, 0x210E848A)))
                {
                    puts("123");
                }
                newRuntimeAddress += encoded_length;
            offset += instruction.length;
        }

If you want to change rip-relative movs to movabs you need to do that manually (see my previous comment). Encoder will never do that for you automatically. After I modify some parameters, is it impossible for the program to automatically modify them for me?

injertao commented 2 years ago

210E848A - 8B 05 DC4A9D01 - mov eax,[22ABCF6C] I want to expand it to 9 bytes or 10 bytes, because in the new runtime-address, its relative address may be greater than 4 bytes

mappzor commented 2 years ago

Your code does exactly what you expect. In this case (address in first 4GB of address space) encoder is able to deal with it just with 6 bytes but code will behave correctly for any address.

injertao commented 2 years ago

@mappzor I have an unreasonable trick, req.operands[i].mem.displacement = absolute_address; When I add a piece of code req.operands[i].mem. displacement += 0x100000000 ZydisEncoderEncodeInstructionAbsolutecan return the result I expect, 9 bytes, and then I execute: req.operands[i].mem. displacement -= 0x100000000

injertao commented 2 years ago

@flobernd @mappzor I think ZydisGetUnsignedImmSizeis not perfect in obtaining size, perhaps imm<=ZYAN_ UINT32_ MAX, but I need an 8-byte slot and need to return 64 bytes. When it comes to absolute addresses, 4 bytes obviously cannot meet my requirements Like this:1DE12636B3A - A1 6CCFAB2200000000 - mov eax,[22ABCF6C] 0x22ABCF6C <= ZYAN_ UINT32_ MAX but,6CCFAB2200000000 == 64 The size should be calculated according to absolute_address(0x1DE12636B3A) and absolute_address(0x22ABCF6C)

mappzor commented 2 years ago

I need an 8-byte slot

No, you don't because 0x22ABCF6C fits in 4 bytes.

The size should be calculated according to absolute_address(0x1DE12636B3A) and absolute_address(0x22ABCF6C)

And it is. Try passing 0x1DE12636B3A and you will get a 9-byte instruction. Encoder picks the shortest encoding that can do the job.

injertao commented 2 years ago

No, you don't because 0x22ABCF6C fits in 4 bytes. I'm sure I need 8 bytes. Although 0x22ABCF6C is 4 bytes, when the runtime address is greater than 0xFFFFFFFF, the relative addressing is obviously greater than 0x7FFFFFFF, And it is. Try passing 0x1DE12636B3A and you will get a 9-byte instruction. Encoder picks the shortest encoding that can do the job. 1DE12636B3A - A1 6CCFAB2200000000 - mov eax,[22ABCF6C] You've misunderstood.,Obviously, 1DE12636B3Aand 22ABCF6Care not of the same type

mappzor commented 2 years ago

You have changed runtime address instead of displacement, so I don't know what were you expecting with that change. You are using absolute mov instead of rip-relative, so runtime address doesn't matter. It seems you don't really understand the difference between the two and tried to bruteforce your way through this. Encoder will pick correct, shortest encoding for the requested mov variant:

memset(&req, 0, sizeof(req));
req.machine_mode = ZYDIS_MACHINE_MODE_LONG_64;
req.mnemonic = ZYDIS_MNEMONIC_MOV;
req.operand_count = 2;
req.operands[0].type = ZYDIS_OPERAND_TYPE_REGISTER;
req.operands[0].reg.value = ZYDIS_REGISTER_EAX;
req.operands[1].type = ZYDIS_OPERAND_TYPE_MEMORY;
req.operands[1].mem.size = 4;
req.operands[1].mem.displacement = 0x22ABCF6C;
// Produces: 67 A1 6C CF AB 22

req.machine_mode = ZYDIS_MACHINE_MODE_LONG_64;
req.mnemonic = ZYDIS_MNEMONIC_MOV;
req.operand_count = 2;
req.operands[0].type = ZYDIS_OPERAND_TYPE_REGISTER;
req.operands[0].reg.value = ZYDIS_REGISTER_EAX;
req.operands[1].type = ZYDIS_OPERAND_TYPE_MEMORY;
req.operands[1].mem.size = 4;
req.operands[1].mem.displacement = 0x1DE12636B3A;
// Produces: A1 3A 6B 63 12 DE 01 00 00

For further information about adressing modes consult Intel Manual:

You won't progress without understanding key difference between those adressing modes. It's essential for binary rewriting and require careful handling of different possible cases. It seems to me that your problem is more about understanding the ISA itself rather than Zydis.

injertao commented 2 years ago

@mappzor Thank you very much for your answer. I'd better try to use violence

ZyanU8 ZydisGetUnsignedImmSize(ZyanU64 imm, ZyanU64 new_runtime_address)
{
    if (new_runtime_address != NULL)
    {
        imm = imm > new_runtime_address ? imm - new_runtime_address : new_runtime_address - imm;
    }
    if (imm <= ZYAN_UINT8_MAX)
    {
        return 8;
    }
    if (imm <= ZYAN_UINT16_MAX)
    {
        return 16;
    }
    if (imm <= ZYAN_UINT32_MAX)
    {
        return 32;
    }
    return 64;

}

You are very nice