콘텐츠로 건너뛰기

함수 호출시 매개변수와 리턴값 살펴보기 (Linux)

add.c

  • Build: gcc -fno-pie -no-pie -o add add.c
  • Code
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    printf("2 + 3 = %d\n", add(2, 3));
    return 0;
}

DynmaicRIO

Thanks, 🥔

https://github.com/DynamoRIO/dynamorio

  • 정적 바이너리에도 적용할 수 있어 좋음

Environment: Ubuntu 20.04 LTS

  • Add func address: 0x0000000000401cb5
ubuntu@64a54bd8db27:~/study/DynamoRIO-Linux-11.3.0-1$ nm add | grep add
...
0000000000401cb5 T add
...
  • test.c
#include "dr_api.h"
#include "drwrap.h"
#include "drsyms.h"

// 전처리 콜백
static void pre(void *wrapcxt, DR_PARAM_OUT void **user_data) {
    void *arg0 = drwrap_get_arg(wrapcxt, 0);
    void *arg1 = drwrap_get_arg(wrapcxt, 1);
    dr_fprintf(STDERR, "Calling add func with arg0=%p, arg1: %p\n", arg0, arg1);
}

// 후처리 콜백
static void post(void *wrapcxt, void *user_data) {
    void *ret = drwrap_get_retval(wrapcxt);
    dr_fprintf(STDERR, "add func returned %p\n", ret);
}

static void event_exit(void) {
    drwrap_exit();
}

static void module_load_event(void *drcontext, const module_data_t *mod, bool loaded) {
    app_pc addr = (app_pc)0x000000000401cb5;
    if (drwrap_wrap(addr, pre, post))
        dr_printf("Wrapped add at %p\n", addr);
}

DR_EXPORT void dr_client_main(client_id_t id, int argc, const char *argv[]) {
    drwrap_init();
    drsym_init(0);
    dr_register_exit_event(event_exit);
    dr_register_module_load_event(module_load_event);
}
  • build.sh
ubuntu@64a54bd8db27:~/study/DynamoRIO-Linux-11.3.0-1$ 
g++ -shared -fPIC -o test.so test.c -I ./include -I./ext/include -L ./lib64 -L ./lib64/release -L ./ext/lib64/release -DLINUX -DX86_64 -DUNIX -ldynamorio -ldrwrap -ldrsyms
  • Result
ubuntu@64a54bd8db27:~/study/DynamoRIO-Linux-11.3.0-1$ 
./bin64/drrun -c ./test.so -- ./add
Wrapped add at 0x0000000000401cb5
Wrapped add at 0x0000000000401cb5
Wrapped add at 0x0000000000401cb5
Wrapped add at 0x0000000000401cb5
Calling add func with arg0=0x0000000000000002, arg1: 0x0000000000000003
add func returned 0x0000000000000005
2 + 3 = 5

Patchrex2

https://github.com/purseclab/Patcherex2

  • 바이너리 패치를 통해 매개변수를 살펴볼 수 있음.
  • 단, (거의) 함수 프롤로그 위주에만 적용 가능한 것 같음. 리턴값은 보기 불가능한듯.

Environment: Ubuntu 22.04 LTS

  • Configure
sudo su 

wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main" | tee /etc/apt/sources.list.d/llvm.list

apt-get update && apt-get install -y clang-19 lld-19

exit;

pip3 install patcherex2

git clone 
  • Add func address: 0x0000000000001149
ubuntu@wh1te4ever-main:~/Patcherex2/examples/insert_instruction_patch_c$ 
nm ./add | grep add
0000000000001149 T add

ubuntu@wh1te4ever-main:~/Patcherex2/examples/insert_instruction_patch_c$ 
objdump -d  ./add

0000000000001149 <add>:
    1149:	f3 0f 1e fa          	endbr64 
    114d:	55                   	push   %rbp
    114e:	48 89 e5             	mov    %rsp,%rbp
    1151:	89 7d fc             	mov    %edi,-0x4(%rbp)
    1154:	89 75 f8             	mov    %esi,-0x8(%rbp)
    1157:	8b 55 fc             	mov    -0x4(%rbp),%edx
    115a:	8b 45 f8             	mov    -0x8(%rbp),%eax
    115d:	01 d0                	add    %edx,%eax
    115f:	5d                   	pop    %rbp
    1160:	c3                   	ret 
from patcherex2 import *
import logging

logger = logging.getLogger("patcherex2.patches.instruction_patches")
logger.setLevel(logging.INFO)

p = Patcherex("add", target_opts={"compiler": "clang19"})

c_forward_header = """
// This string will be inserted outside the micropatch function. It will be inserted before your code.
// This is how you can define types and function forward declarations used by your C micropatch
#include <stdio.h>
"""

# The asm_header is inserted in the main body of the patch before the C code. This header is primarily
# useful for gaining access to the stack pointer, which is a register that is unavailable in our C
# code. In this example we have moved rsp to the r12 register, which is a register that is accessible.
# This means that inside the C code we can access variables on the stack by using the r10 variable.
# There is also an asm_footer
asm_header = "mov r12, rsp"

# We can access assembly registers directly by using their name, while still using high level C constructs
# as well as intermediate variables. Note that you can use a return statement anywhere in your C micropatch
# to jump back to the next instruction after the micropatch insertion point.
c_str = """
printf("add called, rsp: %p\\n", (void *) r12);
printf("add called, rdi: 0x%llu, rsi: 0x%llu\\n", rdi, rsi);
"""

# It is generally a good idea to mark some registers as scratch to give the compiler
# breathing room for allocating registers to use for intermediate variables in your micropatch
# All of the registers that we mark as scratch can be freely clobbered by the compiler
# Note that you can still read from scratch registers stored in the variables. What the scratch
# register denotation will indicate however is that the register can be re-used after the variable
# is no longer live.
c_scratch_regs = [
    'r8', 'r9', 'r10', 'r11', 'r13', 'r14', 'r15'
    'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14', 'xmm15'
]

# By default floating point registers will have the 'float' type. We can use c_regs_sort to override
# certain registers so they hold different types. In this example we denote that xmm8 is of type double
c_regs_sort = [('xmm8', 'double')]

config = InsertInstructionPatch.CConfig(
    c_forward_header = c_forward_header,
    scratch_regs=c_scratch_regs,
    regs_sort=c_regs_sort,
    asm_header=asm_header
)

p.patches.append(InsertInstructionPatch(0x114d, c_str, language="C", c_config=config))
p.apply_patches()

p.binfmt_tool.save_binary()
  • Result
ubuntu@wh1te4ever-main:~/Patcherex2/examples/insert_instruction_patch_c$ 
python3 patch.py
INFO     | 2025-04-22 19:42:21,261 | patcherex2.patches.instruction_patches | InsertInstructionPatch generated C code:
...

ubuntu@wh1te4ever-main:~/Patcherex2/examples/insert_instruction_patch_c$ 
./add.patched 
add called, rsp: 0x7ffc1cfc1ef8
add called, rdi: 0x2, rsi: 0x3
2 + 3 = 5

Dobby

https://github.com/jmpews/Dobby

git clone https://github.com/jmpews/Dobby.git

cd Dobby
git checkout d353eea7594d1ac85db84065aeb4d1300d9f3248

nano ~/Dobby/source/MemoryAllocator/NearMemoryAllocator.cc

MemBlock *NearMemoryAllocator::allocateNearBlockFromDefaultAllocator,
**MemBlock *NearMemoryAllocator::allocateNearBlockFromUnusedRegion 함수에 있는 코드 수정.

min_valid_addr = pos - search_range;  // if pos < search_range, and min_valid_addr will be a large value,this maybe a bug
max_valid_addr = pos + search_range;

위 내용 다음에 아래 내용 추가

min_valid_addr = pos >= search_range ? (pos - search_range) : 0;

설치

mkdir build_for_host && cd build_for_host
cmake ..
make -j8
sudo cp libdobby.so /usr/local/lib
sudo ldconfig
sudo cp ~/Dobby/include/dobby.h /usr/local/include
  • build.sh
#!/bin/bash
gcc -shared -fPIC -o hook.so hook.c -ldl -L. -ldobby
  • hook.c
  • Add func address: 0x0000000000401139
#define _GNU_SOURCE
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <dobby.h>



void add_handler(void *address, DobbyRegisterContext *reg_ctx) {
    uint64_t rdi = (uint64_t)(reg_ctx->general.regs.rdi);
    uint64_t rsi = (uint64_t)(reg_ctx->general.regs.rsi);

    printf("[hook.so] Called add_handler: rdi = 0x%lx, rsi = 0x%lx\n", rdi, rsi);
}



__attribute__((constructor)) 
void initialize() {
    printf("[hook.so] Successfully Loaded!\n");

    dobby_enable_near_branch_trampoline();
    DobbyInstrument((void *)(0x0000000000401136), (dobby_instrument_callback_t)add_handler);
	dobby_disable_near_branch_trampoline();

    // DobbyHook((void*)(0x7165e), (void*)hook_main, (void**)&orig_main);

}
  • Result (hook.c)
ubuntu@64a54bd8db27:~/study/DynamoRIO-Linux-11.3.0-1$ LD_PRELOAD=./hook.so ./add
[hook.so] Successfully Loaded!
[hook.so] Called add_handler: rdi = 0x2, rsi = 0x3
2 + 3 = 5
  • hook2.c
#define _GNU_SOURCE
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <dobby.h>

static int (*orig_add)(int a, int b);
static int hook_add(int a, int b) {
    printf("[hook.so] hook_add a: %d, b: %d\n", a, b);
    return orig_add(a, b);
}


__attribute__((constructor)) 
void initialize() {
    printf("[hook.so] Successfully Loaded!\n");

    DobbyHook((void*)(0x401136), (void*)hook_add, (void**)&orig_add);
}
  • Result (hook2.c)
ubuntu@64a54bd8db27:~/study/DynamoRIO-Linux-11.3.0-1$ LD_PRELOAD=./hook.so ./add
[hook.so] Successfully Loaded!
[hook.so] hook_add a: 2, b: 3
2 + 3 = 5
태그: