0

I'm curious if there's a way to use __asm in c++ then write that into memory instead of doing something like:

BYTE shell_code[] = { 0x48, 0x03 ,0x1c ,0x25, 0x0A, 0x00, 0x00, 0x00  };
write_to_memory(function, &shell_code, sizeof(shell_code));

So I would like to do:

 asm_code = __asm("add rbx, &variable\n\t""jmp rbx") ;
 write_to_memory(function, &asm_code , sizeof(asm_code ));

Worst case I can use GCC and objdump externally or something but hoping there's an internal way

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 2
    Are you looking for something more than GCC's `asm` support? https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C – aschepler Apr 09 '21 at 03:32
  • The code segment is usually read-only. Where are you writing this to where it could conceivably be run? – tadman Apr 09 '21 at 03:47
  • Is `&variable` supposed to be `OFFSET variable`, i.e. the address of a symbol as an immediate? `&variable` isn't valid syntax in any assembler I've ever seen. Also, do you actually need to `jmp` out of a GNU C `asm("")` statement? (Or out of wherever you're trying to write this to?) Why would `function` have any existing value in RBX to start with, that you could add an address to and get a useful pointer to jump to? – Peter Cordes Apr 09 '21 at 03:51
  • Is `function` itself only called from hand-written asm with a custom calling convention, that passes something in RBX? And it's in writeable + executable memory? Are you creating a special-case JIT or something? – Peter Cordes Apr 09 '21 at 03:57
  • 4
    You *could* put an `asm("")` statement at global scope, with start/end labels inside it, and declare those labels as `char start_code[], end_code[0];`, and memcpy from there. Is that the kind of thing you're looking for? IDK why you wouldn't just use inline asm inside a function if you want hand-written asm to run normally, though. – Peter Cordes Apr 09 '21 at 04:00

2 Answers2

1

You can put an asm(""); statement at global scope, with start/end labels inside it, and declare those labels as extern char start_code[], end_code[0]; so you can access them from C. C char arrays work most like asm labels, in terms of being able to use the C name and have it work as an address.

// compile with gcc -masm=intel
// AFAIK, no way to do that with clang
asm(
  ".pushsection .rodata        \n"  // we don't want to run this from here, it's just data
  "start_code:                 \n"
  "   add rax, OFFSET variable \n" // *absolute* address as 32-bit sign-extended immediate
  "end_code:                   \n"
  ".popsection"
);

__attribute__((used)) static int variable = 1;

extern char start_code[], end_code[0];            // C declarations for those asm labels

#include <string.h>
void copy_code(void *dst)
{
    memcpy(dst, start_code, end_code - start_code);
}

It would be fine to have the payload code in the default .text section, but we can put it in .rodata since we don't want to run it.

Is that the kind of thing you're looking for? asm output on Godbolt (without assembling + disassembling:

start_code:                 
   add rax, OFFSET variable 
end_code:                   
copy_code(void*):
        mov     edx, OFFSET FLAT:end_code
        mov     esi, OFFSET FLAT:start_code
        sub     rdx, OFFSET FLAT:start_code
        jmp     [QWORD PTR memcpy@GOTPCREL[rip]]

To see if it actually assembles to what we want, I compiled with
gcc -O2 -fno-plt -masm=intel -fno-pie -no-pie -c foo.c to get a .o. objdump -drwC -Mintel shows:

0000000000000000 <copy_code>:
   0:   ba 00 00 00 00          mov    edx,0x0  1: R_X86_64_32  .rodata+0x6
   5:   be 00 00 00 00          mov    esi,0x0  6: R_X86_64_32  .rodata
   a:   48 81 ea 00 00 00 00    sub    rdx,0x0  d: R_X86_64_32S .rodata
  11:   ff 25 00 00 00 00       jmp    QWORD PTR [rip+0x0]        # 17 <end_code+0x11>  13: R_X86_64_GOTPCRELX  memcpy-0x4

And with -D to see all sections, the actual payload is there in .rodata, still not linked yet:

Disassembly of section .rodata:

0000000000000000 <start_code>:
   0:   48 05 00 00 00 00       add    rax,0x0  2: R_X86_64_32S .data

-fno-pie -no-pie is only necessary for the 32-bit absolute address of variable to work. (Without it, we get two RIP-relative LEAs and a sub rdx, rsi. Unfortunately neither way of compiling gets GCC to subtract the symbols at build time with mov edx, OFFSET end_code - start_code, but that's just in the code doing the memcpy, not in the machine code being copied.)


In a linked executable

We can see how the linker filled in those relocations. (I tested by using -nostartfiles instead of -c - I didn't want to run it, just look at the disassembly, so there was not point to actually writing a main.)

$ gcc -O2 -fno-plt -masm=intel -fno-pie -no-pie -nostartfiles foo.c
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
$ objdump -D -rwC -Mintel a.out 
(manually edited to remove uninteresting sections)

Disassembly of section .text:

0000000000401000 <copy_code>:
  401000:       ba 06 20 40 00          mov    edx,0x402006
  401005:       be 00 20 40 00          mov    esi,0x402000
  40100a:       48 81 ea 00 20 40 00    sub    rdx,0x402000
  401011:       ff 25 e1 2f 00 00       jmp    QWORD PTR [rip+0x2fe1]        # 403ff8 <memcpy@GLIBC_2.14>

The linked payload:

0000000000402000 <start_code>:
  402000:       48 05 18 40 40 00       add    rax,0x404018    # from add rax, OFFSET variable

0000000000402006 <end_code>:
  402006:       48 c7 c2 06 00 00 00    mov    rdx,0x6
            # this was from  mov rdx, OFFSET end_code - start_code  to see if that would assemble + link

Our non-zero-init dword variable that we're taking the address of:

Disassembly of section .data:

0000000000404018 <variable>:
  404018:       01 00                   add    DWORD PTR [rax],eax
        ...

Your specific asm instruction is weird

&variable isn't valid asm syntax, but I'm guessing you wanted to add the address?

Since you're going to be copying the machine code somewhere, you must avoid RIP-relative addressing modes and any other relative references to things outside the block you're copying. Only mov can use 64-bit absolute addresses, like movabs rdi, OFFSET variable instead of the usual lea rdi, [rip + variable]. Also, you can even load / store into/from RAX/EAX/AX/AL with 64-bit absolute addresses movabs eax, [variable]. (mov-immediate can use any register, load/store are only the accumulator. https://www.felixcloutier.com/x86/mov)

(movabs is an AT&T mnemonic, but GAS allows it in .intel_syntax noprefix to force using 64-bit immediates, instead of the default 32-bit-sign-extended.)

This is kind of opposite of normal position-independent code, which works when the whole image is loaded at an arbitrary base. This will make code that works when the image is loaded to a fixed base (or even variable since runtime fixups should work for symbolic references), and then copied around relative to the rest of your code. So all your memory refs have to be absolute, except for within the asm.

So we couldn't have made PIE-compatible machine code by using lea rdx, [RIP+variable] / add rax, rdx - that would only get the right address for variable when run from the linked location in .rodata, not from any copy. (Unless you manually fixup the code when copying it, but it's still only a rel32 displacement.)


Terminology:

An opcode is part of a machine instruction, e.g. add ecx, 123 assembles to 3 bytes: 83 c1 7b. Those are the opcode, modrm, and imm8 respectively. (https://www.felixcloutier.com/x86/add).

"opcode" also gets misused (especially in shellcode usage) to describe the whole instruction represented as bytes.

Text names for instructions like add are mnemonics.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
0

this is just a guess, i don't know if it will work. i'm sorry in advance for an ugly answer since i don't have much time due to work.

i think you can enclose your asm code inside labels. get the address of that label and the size. treat it as a blob of data and you can write it anywhere.

void funcA(){

    //some code here.

labelStart:

    __asm("
    
        ;asm code here.
        
    ")


labelEnd:

    //some code here.
    
    
    //---make code as movable data.
    char* pDynamicProgram = labelStart;
    size_t sizeDP = labelEnd - labelStart;
        
    //---writing to some memory.
    char* someBuffer = malloc(sizeDP);
    memcpy(someBuffer, pDynamicProgram, sizeDP);
    
    //---execute: cast as a function pointer then execute call.
    ((func*)someBuffer)(/* parameters if any*/);
    

}

the sample code above of course is not compilable. but the logic is kind of like that. i see viruses do it that way though i haven't saw the actual c++ code. but we saw it from disassemblers. for the "return" logic after the call, there are many adhoc ways to do that. just be creative.

also, i think you have to enable first some settings for your program to write to some forbidden memory in case you want to override an existing function.

acegs
  • 2,621
  • 1
  • 22
  • 31
  • If that's the kind of thing you're trying to do, you don't want C `goto` labels inside a function. You'd want asm labels inside the `asm` statement at global scope. (And then declare `extern asm_code[]` to access the label from C, [like I commented on the question](https://stackoverflow.com/questions/67014666/is-it-possible-to-write-asm-in-c-with-opcode-instead-of-shellcode#comment118456406_67014666) to ask if that was the kind of thing the querent wanted.) – Peter Cordes Apr 26 '21 at 03:37