From fb5d501d88be398467548dc1a7cef5184c7b8551 Mon Sep 17 00:00:00 2001 From: adeliktas Date: Sat, 28 Oct 2023 03:55:19 +0200 Subject: [PATCH] improved base and added PP defines KERNEL_UPSTREAM TESTING DEBUG --- .gitignore | 15 +- .gitmodules | 6 - Makefile | 36 --- README.md | 87 ++++--- km/CMakeLists.txt | 30 +++ km/Makefile | 7 + km/TaxiDriver.c | 394 ++++++++++++++++++++++++++++++ km/install.sh | 12 + prepare_imgui.sh | 5 - src/TaxiDriver.c | 199 --------------- src/client/Makefile | 24 -- src/client/communication_struct.h | 26 -- src/client/imgui | 1 - src/client/main.cpp | 23 -- src/client/overlay.cpp | 99 -------- src/client/overlay.hpp | 3 - um/Makefile | 19 ++ um/communication_struct.h | 41 ++++ um/main.cpp | 67 +++++ {src/client => um}/memory.cpp | 59 ++++- {src/client => um}/memory.hpp | 18 +- um/test/Makefile | 35 +++ um/test/addr.txt | 1 + um/test/main.cpp | 44 ++++ um/test/math_module.cpp | 24 ++ um/test/math_module.h | 14 ++ 26 files changed, 813 insertions(+), 476 deletions(-) delete mode 100644 .gitmodules delete mode 100644 Makefile create mode 100644 km/CMakeLists.txt create mode 100644 km/Makefile create mode 100644 km/TaxiDriver.c create mode 100755 km/install.sh delete mode 100755 prepare_imgui.sh delete mode 100644 src/TaxiDriver.c delete mode 100644 src/client/Makefile delete mode 100644 src/client/communication_struct.h delete mode 160000 src/client/imgui delete mode 100644 src/client/main.cpp delete mode 100644 src/client/overlay.cpp delete mode 100644 src/client/overlay.hpp create mode 100644 um/Makefile create mode 100644 um/communication_struct.h create mode 100644 um/main.cpp rename {src/client => um}/memory.cpp (54%) rename {src/client => um}/memory.hpp (70%) create mode 100644 um/test/Makefile create mode 100644 um/test/addr.txt create mode 100644 um/test/main.cpp create mode 100644 um/test/math_module.cpp create mode 100644 um/test/math_module.h diff --git a/.gitignore b/.gitignore index dfe16e4..468018e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ -*.order -*.symvers -Revird -*.ko -TaxiDriver.mod.c -imgui.ini *.o *.cmd +*.symvers +*.ko +*.mod* +*.order +km/build/* +um/main +um/test/test_app +um/test/libmath_module.so +imgui.ini \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 830ab12..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "imgui"] - path = imgui - url = https://github.com/ocornut/imgui/ -[submodule "src/client/imgui"] - path = src/client/imgui - url = https://github.com/ocornut/imgui/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 0dde011..0000000 --- a/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# Makefile for Linux Kernel Driver -CONFIG_MODULE_SIG=n - -# Source files and object files -SRC_DIR := src -SOURCE_FILES := $(wildcard $(SRC_DIR)/*.c) -OBJ_FILES := src/TaxiDriver.o -obj-m := $(OBJ_FILES) -# KERNELDIR ?= /home/maxime/Downloads/linux-6.5.7-arch1/ -KERNELDIR ?= /lib/modules/6.5.8-arch1-1/build/ -# Kernel module name -MODULE_NAME := TaxiDriver - -all: default - -default: - $(MAKE) -C $(KERNELDIR) M=$(PWD) modules - mv src/$(MODULE_NAME).ko . - $(MAKE) clean - cd src/client && $(MAKE) - -clean: - find src/ -maxdepth 1 -type f ! -name "*.h" ! -name "*.c" -exec rm {} \; - -fclean: clean - find . -maxdepth 1 -type f ! -name "Makefile" -exec rm {} \; - -load: - sudo insmod $(MODULE_NAME).ko - sudo mknod /dev/$(MODULE_NAME) c 511 0 - -unload: - sudo rmmod $(MODULE_NAME) - sudo rm /dev/$(MODULE_NAME) - -.PHONY: all clean fclean load unload diff --git a/README.md b/README.md index daa2ab5..b76d566 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,78 @@ # TaxiDriver -W/RPM Driver and usermode for Linux.
-Some things in the client are still not finished : -* X11 overlay +Linux Kernel Module for RPM/WPM and usermode driver with examples. -# Features +## Features * Get the base address of any process/loaded .so file * RPM * WPM -# Compiling -To compile the driver you'll need to be at least on kernel version v6.5.8 (i used the [arch kernel](https://github.com/archlinux/linux/releases/tag/v6.5.8-arch1)).
- -## Prerequises : -You'll need git, linux-headers, SDL3 and OpenGL : +## Compiling +### Prerequises : ``` -sudo pacman -S git linux-headers OpenGL +sudo pacman -S git linux-headers OpenGL cmake yay -S sdl3-git ``` +### Upstream Linux Kernel +To compile the driver you'll need to be at least on kernel version v6.5 (i used the [arch kernel](https://github.com/archlinux/linux/releases/tag/v6.5.8-arch1)).
+### legacy Linux Kernel +To build for an older Linux Kernel ex. 5.15 change KERNEL_UPSTREAM preprocessor definition +in TaxiDriver.h: +`#define KERNEL_UPSTREAM 0` +or if you use cmake only change required in CmakeLists.txt: +`add_compile_definitions(TaxiDriver PRIVATE KERNEL_UPSTREAM=0)` -## Compiling and running : -Clone the repo, prepare imgui (still in active dev) : +### Compiling and running : ``` -git clone https://github.com/ALittlePatate/TaxiDriver --recursive -chmod +x prepare_imgui.sh -./prepare_imgui.sh +git clone --recursive https://github.com/ALittlePatate/TaxiDriver ``` -Compile : +#### Kernel Module +choose and write a MAJOR number ex. 506 in install.sh and validate for uniqueness `grep "506" /proc/devices` ``` -make +# sudo mknod /dev/TaxiDriver c 0 +# cmake is still incomplete +cd km && ./install.sh ``` -Run the driver : +system might still choose to set a different one, so read the number that is in use, after TaxiDriver is loaded, +with `grep "TaxiDriver" /proc/devices` and adjust in mknod and reload TaxiDriver. +After loading the Driver, you should find a line saying that TaxiDriver was loaded with X major code. ``` -sudo make load sudo dmesg | grep TaxiDriver ``` -You should find a line saying that TaxiDriver was loaded with X major code. -``` -sudo mknod /dev/TaxiDriver c X 0 -``` -replace X with your major code.
-Run the client : -``` -sudo ./Revird -``` Unload the driver : ``` -sudo make unload +# sudo make unload +sudo rmmod TaxiDriver +sudo rm /dev/TaxiDriver ``` -# Contact +#### Usermode Target/Test Application +``` +cd um/test && make +./test_app +``` + +#### Usermode Driver Application +``` +cd um && make +./main +``` + + +### Known Issues +- Fails to open device: +MAJOR number has to match in mknod to open device! Check with `grep "TaxiDriver" /proc/devices` + +If you find anything not listed above. Please open up Issues on the Repo +and provide the build log showing the issue with `LANG=C make` with all of your steps and envoirment info to reproduce. + +### Missing Features in development +- ImGui Overlay +- expand km mem functions +- expand um driver example and test_app + - Findpattern + - write/inject shellcode with asm mid function hook +- X11 Input Event Handling + +## Contact If you have a problem regarding the code, open an issue or make a pull request, i'll be happy to add your contribution !
-If you need to contact me for any other reasons, message me at @_.patate on discord. +If you need to contact me for any other reasons, message me at `_.patate` on discord. diff --git a/km/CMakeLists.txt b/km/CMakeLists.txt new file mode 100644 index 0000000..a3d1631 --- /dev/null +++ b/km/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.12) +project(TaxiDriverModule) + +# Set the source files for your module +set(SRC ${CMAKE_SOURCE_DIR}/TaxiDriver.c) + +# Locate the kernel build directory +execute_process( + COMMAND uname -r + OUTPUT_VARIABLE KERNEL_RELEASE + OUTPUT_STRIP_TRAILING_WHITESPACE +) +set(KERNEL_BUILD_DIR /lib/modules/${KERNEL_RELEASE}/build) + +# Define the target +add_custom_target(TaxiDriverModule ALL + COMMAND ${CMAKE_MAKE_PROGRAM} -C ${KERNEL_BUILD_DIR} M=${CMAKE_BINARY_DIR} modules + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Building TaxiDriver module" +) + +# Define the clean target +add_custom_target(clean_TaxiDriverModule + COMMAND ${CMAKE_MAKE_PROGRAM} -C ${KERNEL_BUILD_DIR} M=${CMAKE_BINARY_DIR} clean + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Cleaning TaxiDriver module" +) + +# Specify the files to be cleaned +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_BINARY_DIR}/*.ko") diff --git a/km/Makefile b/km/Makefile new file mode 100644 index 0000000..1863457 --- /dev/null +++ b/km/Makefile @@ -0,0 +1,7 @@ +obj-m += TaxiDriver.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean \ No newline at end of file diff --git a/km/TaxiDriver.c b/km/TaxiDriver.c new file mode 100644 index 0000000..d927f32 --- /dev/null +++ b/km/TaxiDriver.c @@ -0,0 +1,394 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include // Add this header for basename function + +#define KERNEL_UPSTREAM 0 +#define TESTING 0 +#define DEBUG 0 + +#if KERNEL_UPSTREAM==1 +#include +#else +#include +#include +#endif + +#define DRIVER_NAME "TaxiDriver" +#define DRIVER +#include "../um/communication_struct.h" + +static int major_number; +static struct task_struct *task; + +static int device_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int device_release(struct inode *inode, struct file *file) +{ + return 0; +} + +#if TESTING == 1 +static unsigned long translate_physical_to_virtual(unsigned long paddr) { + unsigned long vaddr = 0xffff888000000000 | (paddr & (PAGE_SIZE - 1)); + return vaddr; +} +#endif + +#if TESTING == 1 +static unsigned long translate_virtual_to_physical(struct mm_struct *mm, unsigned long vaddr) { + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + pte_t *pte; + unsigned long pfn; + unsigned long paddr = 0; + + if (!mm) + return 0; // Handle invalid mm_struct + + pgd = pgd_offset(mm, vaddr); + if (!pgd_present(*pgd)) + return 0; // Handle invalid pgd + + p4d = p4d_offset(pgd, vaddr); + if (!p4d_present(*p4d)) + return 0; // Handle invalid p4d + + pud = pud_offset(p4d, vaddr); + if (!pud_present(*pud)) + return 0; // Handle invalid pud + + pmd = pmd_offset(pud, vaddr); + if (!pmd_present(*pmd)) + return 0; // Handle invalid pmd + + pte = pte_offset_map(pmd, vaddr); + if (!pte_present(*pte)) + return 0; // Handle invalid pte + + pfn = page_to_pfn(pte_page(*pte)); + paddr = (pfn << PAGE_SHIFT) | (vaddr & ~PAGE_MASK); + pte_unmap(pte); + + return paddr; +} +#endif + +int RPM(t_RPM args) { + struct mm_struct *mm; + unsigned long value = 0; + + printk(KERN_INFO "TaxiDriver: RPM --> addr : 0x%lx, size : %ld\n", args.addr, args.size); + if (!args.addr) + { + return -1; + } + if (!task) { + return -EINVAL; // Invalid argument + } + mm = get_task_mm(task); + if (mm) { + if (access_process_vm(task, args.addr, &value, args.size, 0) == args.size) { + printk(KERN_INFO "TaxiDriver: Value at 0x%lx: %lu\n", args.addr, value); + mmput(mm); + } else { + printk(KERN_ALERT "TaxiDriver: Failed to read value at 0x%lx\n", args.addr); + return -1; + } + } + return (int)value; +} + +int WPM(t_WPM args) { + struct mm_struct *mm; + int ret = 0; + + printk(KERN_INFO "TaxiDriver: WPM --> addr : 0x%lx, size : %ld, value : %ld\n", + args.addr, args.size, args.value); + if (!args.addr) + return -1; + if (!task) { + return -EINVAL; // Invalid argument + } + mm = get_task_mm(task); + if (mm) { + if (access_process_vm(task, args.addr, &args.value, args.size, 1) == args.size) { + printk(KERN_INFO "TaxiDriver: Successfully wrote value %lu to 0x%lx\n", args.value, args.addr); + mmput(mm); + } else { + printk(KERN_ALERT "TaxiDriver: Failed to write value at 0x%lx\n", args.addr); + ret = -1; + } + } else { + ret = -EINVAL; + } + return ret; +} + +static uintptr_t list_process_modules(const char *mod) { + struct vm_area_struct *vma; +#if KERNEL_UPSTREAM == 1 + struct mm_struct *mm = task->mm; + VMA_ITERATOR(vmi, mm, 0); + + for_each_vma(vmi, vma) { + if (vma->vm_file) { + struct file *file = vma->vm_file; + printk(KERN_INFO "TaxiDriver: Shared Library: %s start: 0x%lx end: 0x%lx\n", + file->f_path.dentry->d_name.name, vma->vm_start, vma->vm_end); + if (strcmp(file->f_path.dentry->d_name.name, mod) == 0) + return (uintptr_t)vma->vm_start; + } + } +#else + struct task_struct *task; + struct mm_struct *mm; + + for_each_process(task) { + mm = get_task_mm(task); + + if (mm) { + printk(KERN_INFO "\nTaxiDriver: PARENT PID: %d PROCESS: %s", task->pid, task->comm); + down_read(&mm->mmap_lock); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + printk(KERN_INFO "TaxiDriver: Address Range: 0x%lx - 0x%lx", vma->vm_start, vma->vm_end); + + if (vma->vm_file) { + char buf[256]; + char *file_name = d_path(&vma->vm_file->f_path, buf, sizeof(buf)); + //const char *base_name = kbasename(file_name); // Extract the base name + printk(KERN_INFO "TaxiDriver: Mapped File: %s", file_name); + //printk(KERN_INFO "TaxiDriver: Mapped File: %s", base_name); + if (strcmp(file_name, mod) == 0) + //if (strcmp(base_name, mod) == 0) // Compare with the base name + //printk(KERN_INFO "TaxiDriver: Mapped File %s found at 0x%lx - 0x%lx", base_name, vma->vm_start, vma->vm_end); + return (uintptr_t)vma->vm_start; + } + } + + up_read(&mm->mmap_lock); + mmput(mm); + } + } +#endif + return 0; +} + +//only checked for KERNEL_UPSTREAM == 0 +#if KERNEL_UPSTREAM == 0 +static unsigned long long list_pid_mod(t_PM args) { + printk(KERN_INFO "\nTaxiDriver list_pid_mod:%s", args.mod); + struct task_struct *task; + struct mm_struct *mm; + struct vm_area_struct *vma; + + for_each_process(task) { + mm = get_task_mm(task); + + if (mm) { + if(task->pid == args.pid) + { + printk(KERN_INFO "\nTaxiDriver: PARENT PID: %d PROCESS: %s", task->pid, task->comm); + } + down_read(&mm->mmap_lock); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + //printk(KERN_INFO "TaxiDriver: Address Range: 0x%lx - 0x%lx", vma->vm_start, vma->vm_end); + if (vma->vm_file) { + char buf[256]; + char *file_name = d_path(&vma->vm_file->f_path, buf, sizeof(buf)); + const char *base_name = kbasename(file_name); + if (strcmp(base_name, args.mod) == 0) + { + unsigned long long vmstartaddr = (unsigned long long)vma->vm_start; + if(vmstartaddr>0) + { + printk(KERN_INFO "TaxiDriver: Mapped File %s found at 0x%lx - 0x%lx", base_name, vma->vm_start, vma->vm_end); + return vmstartaddr; + } + } + } + } + up_read(&mm->mmap_lock); + mmput(mm); + } + } + return 0; +} +#endif + +static int init_process_by_pid(int target_pid) { + printk(KERN_INFO "TaxiDriver: Accessing process with PID: %d\n", target_pid); + + struct pid *pid_struct; + pid_struct = find_get_pid(target_pid); + if (pid_struct) { + task = get_pid_task(pid_struct, PIDTYPE_PID); + if (task) { + const char *process_name = task->comm; + printk(KERN_INFO "TaxiDriver: Process with PID %d has name: %s\n", target_pid, process_name); + put_task_struct(task); + } else { + printk(KERN_INFO "TaxiDriver: Process with PID %d not found\n", target_pid); + return -1; + } + put_pid(pid_struct); + } else { + printk(KERN_INFO "TaxiDriver: Process with PID %d not found\n", target_pid); + return -1; + } + + return 1; // A successful module initialization +} + +static long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long arg) +{ + struct s_WPM wpm_args; + struct s_RPM rpm_args; +#if KERNEL_UPSTREAM == 0 + struct s_PM pm_args; +#endif + const char *mod = kmalloc(sizeof(char) * 256, GFP_KERNEL); + if (!mod) + { + return -ENOMEM; + } + + static uintptr_t addr = 0; + int pid; + long return_value = 0; + + switch (ioctl_num) { + case IOCTL_GETMODULE: + if (copy_from_user((void *)mod, (int *)arg, sizeof(char *))) + return -EFAULT; + return_value = list_process_modules(mod); + addr = return_value; + kfree(mod); + break; +#if KERNEL_UPSTREAM == 0 + case IOCTL_GETPIDMODULE: + if (copy_from_user(&pm_args, (int *)arg, sizeof(t_PM))) + return -EFAULT; + + if (copy_from_user(mod, pm_args.mod, 256)) // ptr/data Userspace to Kernel else perms/page fault + { + return -EFAULT; + } + pm_args.mod = mod; + + return_value = list_pid_mod(pm_args); + addr = return_value; + kfree(mod); + break; +#endif + case IOCTL_OPENPROC: + if (copy_from_user(&pid, (int *)arg, sizeof(int))) + return -EFAULT; + return_value = init_process_by_pid(pid); + break; + + case IOCTL_RPM: + if (copy_from_user(&rpm_args, (int *)arg, sizeof(t_RPM))) + return -EFAULT; + if (rpm_args.addr == 0x69420) { + put_user(addr, rpm_args.out_addr); + break; + } + return_value = RPM(rpm_args); + put_user(return_value, rpm_args.out_addr); + break; + + case IOCTL_WPM: + if (copy_from_user(&wpm_args, (int *)arg, sizeof(t_WPM))) + return -EFAULT; + return_value = WPM(wpm_args); + break; +#if KERNEL_UPSTREAM == 0 && TESTING==1 + case IOCTL_VIRT_TO_PHYS: + { + struct mm_struct *mm = current->mm; // Get the current process's mm_struct + unsigned long vaddr; + unsigned long paddr; + + if (copy_from_user(&vaddr, (unsigned long *)arg, sizeof(unsigned long))) { + return -EFAULT; + } + + paddr = translate_virtual_to_physical(mm, vaddr); + return_value = paddr; + if (copy_to_user((unsigned long *)arg, &return_value, sizeof(unsigned long))) { + return -EFAULT; + } + } + break; +#endif + +#if KERNEL_UPSTREAM == 0 && TESTING==1 + case IOCTL_PHYS_TO_VIRT: + { + unsigned long paddr; + unsigned long vaddr; + + if (copy_from_user(&paddr, (unsigned long *)arg, sizeof(unsigned long))) { + return -EFAULT; + } + + vaddr = translate_physical_to_virtual(paddr); + if (copy_to_user((unsigned long *)arg, &vaddr, sizeof(unsigned long))) { + return -EFAULT; + } + } + break; +#endif + + default: + return -ENOTTY; + } +#if KERNEL_UPSTREAM == 0 && TESTING==1 + printk(KERN_INFO "return_value=0x%llx\n", (unsigned long long)return_value); +#endif + return return_value; +} + +static struct file_operations fops = { + .unlocked_ioctl = device_ioctl, + .open = device_open, + .release = device_release, +}; + +static int __init driver_init(void) +{ + printk(KERN_INFO "TaxiDriver: Loaded\n"); + + // Dynamically allocate the major number + major_number = register_chrdev(0, DRIVER_NAME, &fops); + + if (major_number < 0) { + printk(KERN_ALERT "TaxiDriver: Failed to register the driver.\n"); + return major_number; + } + + printk(KERN_INFO "TaxiDriver: Registered %s with major number %d\n", DRIVER_NAME, major_number); + + return 0; +} + +static void __exit driver_exit(void) +{ + unregister_chrdev(major_number, DRIVER_NAME); + printk(KERN_INFO "TaxiDriver: Unloaded\n"); +} + +module_init(driver_init); +module_exit(driver_exit); +MODULE_LICENSE("GPL"); diff --git a/km/install.sh b/km/install.sh new file mode 100755 index 0000000..1b8b2ff --- /dev/null +++ b/km/install.sh @@ -0,0 +1,12 @@ +#!/bin/bash +make clean +make +sudo rmmod TaxiDriver +sudo rm /dev/TaxiDriver +# choose a unique MAJOR number; read out the used number and adjust in mknod for next use. +# MAJOR number has to match mknod to open device! +# 511, 250, 505 +sudo mknod /dev/TaxiDriver c 506 0 +sudo chmod 666 /dev/TaxiDriver +sudo insmod TaxiDriver.ko +cat /proc/devices | grep -i "TaxiDriver" diff --git a/prepare_imgui.sh b/prepare_imgui.sh deleted file mode 100755 index dc0f191..0000000 --- a/prepare_imgui.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd src/client/imgui/backends -mv imgui_impl_opengl3* .. -mv imgui_impl_sdl3* .. diff --git a/src/TaxiDriver.c b/src/TaxiDriver.c deleted file mode 100644 index 1cf48cf..0000000 --- a/src/TaxiDriver.c +++ /dev/null @@ -1,199 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "TaxiDriver" -#define DRIVER -#include "client/communication_struct.h" - -static int major_number; -static struct task_struct *task; - -static int device_open(struct inode *inode, struct file *file) -{ - return 0; -} - -static int device_release(struct inode *inode, struct file *file) -{ - return 0; -} - -int RPM(t_RPM args) { - struct mm_struct *mm; - unsigned long value = 0; - - printk(KERN_INFO "TaxiDriver: RPM --> addr : 0x%lx, size : %ld\n", args.addr, args.size); - if (args.addr == 0) - return -1; - if (task == NULL) { - return -EINVAL; // Invalid argument - } - mm = get_task_mm(task); - if (mm != NULL) { - if (access_process_vm(task, args.addr, &value, args.size, 0) == args.size) { - printk(KERN_INFO "TaxiDriver: Value at 0x%lx: %lu\n", args.addr, value); - mmput(mm); - } else { - printk(KERN_ALERT "TaxiDriver: Failed to read value at 0x%lx\n", args.addr); - return -1; - } - } - return (int)value; -} - -int WPM(t_WPM args) { - struct mm_struct *mm; - int ret = 0; - - printk(KERN_INFO "TaxiDriver: WPM --> addr : 0x%lx, size : %ld, value : %ld\n", - args.addr, args.size, args.value); - if (args.addr == 0) - return -1; - if (task == NULL) { - return -EINVAL; // Invalid argument - } - mm = get_task_mm(task); - if (mm != NULL) { - if (access_process_vm(task, args.addr, &args.value, args.size, 1) == args.size) { - printk(KERN_INFO "TaxiDriver: Successfully wrote value %lu to 0x%lx\n", args.value, args.addr); - mmput(mm); - } else { - printk(KERN_ALERT "TaxiDriver: Failed to write value at 0x%lx\n", args.addr); - ret = -1; - } - } else { - ret = -EINVAL; - } - return ret; -} - -static uintptr_t list_process_modules(const char *mod) { - struct mm_struct *mm = task->mm; - struct vm_area_struct *vma; - VMA_ITERATOR(vmi, mm, 0); - - for_each_vma(vmi, vma) { - if (vma->vm_file) { - struct file *file = vma->vm_file; - printk(KERN_INFO "TaxiDriver: Shared Library: %s start: 0x%lx end: 0x%lx\n", - file->f_path.dentry->d_name.name, vma->vm_start, vma->vm_end); - if (strcmp(file->f_path.dentry->d_name.name, mod) == 0) - return (uintptr_t)vma->vm_start; - } - } - return 0; -} - -static int init_process_by_pid(int target_pid) { - printk(KERN_INFO "TaxiDriver: Accessing process with PID: %d\n", target_pid); - - struct pid *pid_struct; - pid_struct = find_get_pid(target_pid); - if (pid_struct != NULL) { - task = get_pid_task(pid_struct, PIDTYPE_PID); - if (task != NULL) { - const char *process_name = task->comm; - printk(KERN_INFO "TaxiDriver: Process with PID %d has name: %s\n", target_pid, process_name); - put_task_struct(task); - } else { - printk(KERN_INFO "TaxiDriver: Process with PID %d not found\n", target_pid); - return -1; - } - put_pid(pid_struct); - } else { - printk(KERN_INFO "TaxiDriver: Process with PID %d not found\n", target_pid); - return -1; - } - - return 1; // A successful module initialization -} - -static long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long arg) -{ - struct s_WPM wpm_args; - struct s_RPM rpm_args; - const char *mod = kmalloc(sizeof(char) * 256, GFP_KERNEL); - if (!mod) - return -ENOMEM; - static uintptr_t addr = 0; - int pid; - long return_value = 0; - - switch (ioctl_num) { - case IOCTL_GETMODULE: - if (copy_from_user((void *)mod, (int *)arg, sizeof(char *))) - return -EFAULT; - return_value = list_process_modules(mod); - addr = return_value; - kfree(mod); - break; - - case IOCTL_OPENPROC: - if (copy_from_user(&pid, (int *)arg, sizeof(int))) - return -EFAULT; - return_value = init_process_by_pid(pid); - break; - - case IOCTL_RPM: - if (copy_from_user(&rpm_args, (int *)arg, sizeof(t_RPM))) - return -EFAULT; - if (rpm_args.addr == 0x69420) { - put_user(addr, rpm_args.out_addr); - break; - } - return_value = RPM(rpm_args); - put_user(return_value, rpm_args.out_addr); - break; - - case IOCTL_WPM: - if (copy_from_user(&wpm_args, (int *)arg, sizeof(t_WPM))) - return -EFAULT; - return_value = WPM(wpm_args); - break; - - default: - return -ENOTTY; - } - - return return_value; -} - -static struct file_operations fops = { - .unlocked_ioctl = device_ioctl, - .open = device_open, - .release = device_release, -}; - -static int __init driver_init(void) -{ - printk(KERN_INFO "TaxiDriver: Loaded\n"); - - // Dynamically allocate the major number - major_number = register_chrdev(0, DRIVER_NAME, &fops); - - if (major_number < 0) { - printk(KERN_ALERT "TaxiDriver: Failed to register the driver.\n"); - return major_number; - } - - printk(KERN_INFO "TaxiDriver: Registered %s with major number %d\n", DRIVER_NAME, major_number); - - return 0; -} - -static void __exit driver_exit(void) -{ - unregister_chrdev(major_number, DRIVER_NAME); - printk(KERN_INFO "TaxiDriver: Unloaded\n"); -} - -module_init(driver_init); -module_exit(driver_exit); -MODULE_LICENSE("GPL"); diff --git a/src/client/Makefile b/src/client/Makefile deleted file mode 100644 index 71b748f..0000000 --- a/src/client/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -SRC = main.cpp \ - memory.cpp \ - overlay.cpp \ - imgui/*.cpp -OBJ = $(SRC:.cpp=.o) -NAME = Revird -INC = -Iimgui/ -CFLAGS = -Wall -Wextra -Wpedantic -lSDL3 -lOpenGL - -all: $(NAME) - -$(NAME): $(OBJ) - g++ $(SRC) $(CFLAGS) $(INC) -o $(NAME) - mv $(NAME) ../../. - -clean: - rm -f $(OBJ) - -fclean: clean - rm -f $(NAME) - -re: fclean all - -.PHONY : all $(NAME) clean fclean re diff --git a/src/client/communication_struct.h b/src/client/communication_struct.h deleted file mode 100644 index b9c9dde..0000000 --- a/src/client/communication_struct.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#ifndef DRIVER -#include -#include -#endif - -#define IOCTL_OPENPROC _IOW('k', 1, int) -#define IOCTL_GETMODULE _IOW('k', 2, const char*) -#define IOCTL_RPM _IOW('k', 3, t_RPM) -#define IOCTL_WPM _IOW('k', 4, t_WPM) - -typedef struct s_RPM -{ - uintptr_t addr; - ssize_t size; - uintptr_t out; - uintptr_t *out_addr; -} t_RPM; - -typedef struct s_WPM -{ - uintptr_t addr; - ssize_t size; - uintptr_t value; -} t_WPM; diff --git a/src/client/imgui b/src/client/imgui deleted file mode 160000 index 313676d..0000000 --- a/src/client/imgui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 313676d200f093e2694b5cfca574f72a2b116c85 diff --git a/src/client/main.cpp b/src/client/main.cpp deleted file mode 100644 index 597b78d..0000000 --- a/src/client/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "memory.hpp" -#include "overlay.hpp" -#include - -int main() { - //run_overlay(); - if (!open_device()) - return -1; - - int pid = get_pid("nsnake"); - if (!open_process(pid)) - return -1; - - uintptr_t addr = get_module("nsnake"); - printf("module : 0x%lx\n", addr); - WPM(addr + 0x1d71510, 1337); - - int out = RPM(addr + 0x1d71510); - printf("Value from RPM: %d\n", out); - - close_device(); - return 0; -} diff --git a/src/client/overlay.cpp b/src/client/overlay.cpp deleted file mode 100644 index eaeb382..0000000 --- a/src/client/overlay.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "overlay.hpp" -#include -#include "imgui/imgui.h" -#include "imgui/imgui_impl_sdl3.h" -#include "imgui/imgui_impl_opengl3.h" -#include -#include - -int run_overlay(void) -{ - // Setup SDL - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMEPAD) != 0) - { - printf("Error: SDL_Init(): %s\n", SDL_GetError()); - return -1; - } - - const char* glsl_version = "#version 130"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - - // Enable native IME. - SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); - - // Create window with graphics context - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_UTILITY); - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+OpenGL3 example", 1280, 720, window_flags); - if (window == nullptr) - { - printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); - return -1; - } - SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - SDL_GLContext gl_context = SDL_GL_CreateContext(window); - SDL_GL_MakeCurrent(window, gl_context); - SDL_GL_SetSwapInterval(1); // Enable vsync - SDL_ShowWindow(window); - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - - // Setup Platform/Renderer backends - ImGui_ImplSDL3_InitForOpenGL(window, gl_context); - ImGui_ImplOpenGL3_Init(glsl_version); - - // Main loop - bool done = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - while (!done) - { - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL3_ProcessEvent(&event); - if (event.type == SDL_EVENT_QUIT) - done = true; - if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) - done = true; - } - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL3_NewFrame(); - ImGui::NewFrame(); - - ImGui::ShowDemoWindow(); - - // Rendering - ImGui::Render(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_SwapWindow(window); - } - - // Cleanup - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); - - SDL_GL_DeleteContext(gl_context); - SDL_DestroyWindow(window); - SDL_Quit(); - - return 1; -} diff --git a/src/client/overlay.hpp b/src/client/overlay.hpp deleted file mode 100644 index cc8f84c..0000000 --- a/src/client/overlay.hpp +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -int run_overlay(void); diff --git a/um/Makefile b/um/Makefile new file mode 100644 index 0000000..d3efaa6 --- /dev/null +++ b/um/Makefile @@ -0,0 +1,19 @@ +APP_NAME = main +APP_SRCS = main.cpp memory.cpp +APP_OBJS = $(APP_SRCS:.cpp=.o) + +CXX = g++ +CXXFLAGS = -Wall -Werror + +all: $(APP_NAME) + +$(APP_NAME): $(APP_OBJS) + $(CXX) $(CXXFLAGS) -o $@ $(APP_OBJS) + +clean: + rm -f $(APP_NAME) $(APP_OBJS) + +$(APP_OBJS): %.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +.PHONY: all \ No newline at end of file diff --git a/um/communication_struct.h b/um/communication_struct.h new file mode 100644 index 0000000..a7ec42a --- /dev/null +++ b/um/communication_struct.h @@ -0,0 +1,41 @@ +#pragma once + +#ifndef DRIVER +#include +#include +#endif + +#define IOCTL_OPENPROC _IOW('k', 1, int) +#define IOCTL_GETMODULE _IOW('k', 2, const char*) +#define IOCTL_RPM _IOW('k', 3, t_RPM) +#define IOCTL_WPM _IOW('k', 4, t_WPM) + +#if KERNEL_UPSTREAM == 0 +#define IOCTL_GETPIDMODULE _IOWR('k', 5, t_PM) +#if TESTING == 1 +#define IOCTL_VIRT_TO_PHYS _IOWR('k', 6, unsigned long) +#define IOCTL_PHYS_TO_VIRT _IOWR('k', 7, unsigned long) +#endif +#endif + +typedef struct s_RPM +{ + uintptr_t addr; + ssize_t size; + uintptr_t out; + uintptr_t *out_addr; +} t_RPM; + +typedef struct s_WPM +{ + uintptr_t addr; + ssize_t size; + uintptr_t value; +} t_WPM; + +#if KERNEL_UPSTREAM == 0 +typedef struct s_PM { + int pid; + const char *mod; +} t_PM; +#endif diff --git a/um/main.cpp b/um/main.cpp new file mode 100644 index 0000000..329d75b --- /dev/null +++ b/um/main.cpp @@ -0,0 +1,67 @@ +#include "memory.hpp" + +std::string target_proc_name{"test_app"}; +std::string target_mod_name{"libmath_module.so"}; + +int main() { + //run_overlay(); + if (!open_device()) + return -1; + std::cout << "[Main]device opened" << std::endl; + + int pid = get_pid(target_proc_name.c_str()); + std::cout << "pid of " << target_proc_name << "=" << std::dec << pid << std::endl; + if (!open_process(pid)) + return -1; + + uint64_t addr; + std::ifstream inFile("test/addr.txt"); + if (!inFile.is_open()) { + std::cerr << "Error: Could not open the file for reading." << std::endl; + return 1; + } + inFile >> std::hex; + if (inFile >> addr) { + std::cout << "Read from file var_virtaddr=" << std::hex << addr << std::endl; + } else { + std::cerr << "Error: Failed to read value from file." << std::endl; + return 1; + } + inFile.close(); + +#if KERNEL_UPSTREAM==1 + uint64_t mod_primary_addr = get_module(target_proc_name.c_str()); +#else + uint64_t mod_primary_addr = get_pid_module(pid, target_proc_name.c_str()); +#endif + std::cout << "module of " << target_proc_name << "=" << std::hex << mod_primary_addr << std::endl; + +#if KERNEL_UPSTREAM==1 + uint64_t modaddr_libmath_addr = get_module(target_mod_name.c_str()); +#else + uint64_t modaddr_libmath_addr = get_pid_module(pid, target_mod_name.c_str()); +#endif + std::cout << "module of " << target_mod_name << "=" << std::hex << modaddr_libmath_addr << std::endl; + + //mod_primary_addr = 0; // primary_module not required + int value_read = RPM(0 + addr); + std::cout << "Value before write=" << std::dec << value_read << std::endl; + + WPM(0 + addr, 1337); + value_read = RPM(0 + addr); + std::cout << "Value after write=" << std::dec << value_read << std::endl; + +#if KERNEL_UPSTREAM==0 && TESTING==1 + uint64_t physaddr = VIRT_TO_PHYS(0x7fff2a2cb7a4); + std::cout << "phys addr=" << std::hex << physaddr << std::endl; + uint64_t phys2virtaddr = PHYS_TO_VIRT(0x7fff2a2cb7a4); + std::cout << "phys2virtaddr=" << std::hex << phys2virtaddr << std::endl; + + //attempt to read relative to proc primary module + int value_read_primarymod = RPM(mod_primary_addr + 0x2A917D6757A4); + std::cout << "value_read_primarymod=" << std::dec << value_read_primarymod << std::endl; +#endif + close_device(); + return 0; +} +//pmap -x $(pidof test_app) diff --git a/src/client/memory.cpp b/um/memory.cpp similarity index 54% rename from src/client/memory.cpp rename to um/memory.cpp index 8ca181b..50987bb 100644 --- a/src/client/memory.cpp +++ b/um/memory.cpp @@ -1,12 +1,4 @@ #include "memory.hpp" -#include "communication_struct.h" -#include -#include -#include -#include -#include - -#define DEVICE_FILE "/dev/TaxiDriver" int file_desc = 0; int open_device(void) @@ -35,7 +27,7 @@ int get_pid(const char *program_name) { // Open a pipe to execute the command and read the output fp = popen(command, "r"); - if (fp == NULL) { + if (!fp) { perror("popen"); return -1; } @@ -58,9 +50,7 @@ int get_pid(const char *program_name) { int open_process(int pid) { - int ret; - - ret = ioctl(file_desc, IOCTL_OPENPROC, &pid); + uint64_t ret = ioctl(file_desc, IOCTL_OPENPROC, &pid); if (ret < 0) { perror("Revird: openprocess failed."); close(file_desc); @@ -71,7 +61,7 @@ int open_process(int pid) uintptr_t get_module(const char *mod) { - int ret = ioctl(file_desc, IOCTL_GETMODULE, mod); + uint64_t ret = ioctl(file_desc, IOCTL_GETMODULE, mod); if (ret < 0) { perror("Revird: getmodule failed."); close(file_desc); @@ -81,3 +71,46 @@ uintptr_t get_module(const char *mod) uintptr_t addr = RPM(0x69420); return addr; } + +#if KERNEL_UPSTREAM == 0 +uintptr_t get_pid_module(int pid, const char *mod) { + struct s_PM pma; + pma.pid = pid; + pma.mod = mod; + + uint64_t ret = ioctl(file_desc, IOCTL_GETPIDMODULE, &pma); + if (ret < 0) { + perror("Revird: get_pid_module failed."); + close(file_desc); + return -1; + } + + uintptr_t addr = RPM(0x69420); + return addr; +} +#endif + +#if KERNEL_UPSTREAM == 0 && TESTING==1 +uintptr_t VIRT_TO_PHYS(uintptr_t vaddr) { + uint64_t ret = ioctl(file_desc, IOCTL_VIRT_TO_PHYS, vaddr); + if (ret < 0) { + perror("VIRT_TO_PHYS failed."); + return 0; + } + // At this point, 'vaddr' contains the physical address. + return vaddr; +} +#endif + +#if KERNEL_UPSTREAM == 0 && TESTING==1 +uintptr_t PHYS_TO_VIRT(uintptr_t paddr) { + uint64_t ret = ioctl(file_desc, IOCTL_PHYS_TO_VIRT, &paddr); + if (ret < 0) { + perror("PHYS_TO_VIRT failed."); + return 0; + } + + // At this point, 'paddr' contains the virtual address. + return paddr; +} +#endif diff --git a/src/client/memory.hpp b/um/memory.hpp similarity index 70% rename from src/client/memory.hpp rename to um/memory.hpp index c623d66..427831f 100644 --- a/src/client/memory.hpp +++ b/um/memory.hpp @@ -4,7 +4,12 @@ #include #include #include +//#include "overlay.hpp" +#include +#include +#include #include "communication_struct.h" +#define DEVICE_FILE "/dev/TaxiDriver" extern int file_desc; int open_device(void); @@ -20,7 +25,7 @@ T RPM(uintptr_t address) args.out = 0; args.out_addr = &args.out; - int ret = ioctl(file_desc, IOCTL_RPM, &args); + uint64_t ret = ioctl(file_desc, IOCTL_RPM, &args); if (ret < 0) { perror("Revird: RPM failed."); close(file_desc); @@ -36,9 +41,7 @@ void WPM(uintptr_t address, T value) args_wpm.addr = address; args_wpm.size = sizeof(T); args_wpm.value = value; - int ret; - - ret = ioctl(file_desc, IOCTL_WPM, &args_wpm); + uint64_t ret = ioctl(file_desc, IOCTL_WPM, &args_wpm); if (ret < 0) { perror("Revird: WPM failed."); close(file_desc); @@ -50,3 +53,10 @@ void WPM(uintptr_t address, T value) void WPM(uintptr_t addr, uintptr_t value, ssize_t size); int open_process(int pid); uintptr_t get_module(const char *mod); +#if KERNEL_UPSTREAM == 0 +uintptr_t get_pid_module(int pid, const char *mod); +#if TESTING==1 +uintptr_t VIRT_TO_PHYS(uintptr_t vaddr); +uintptr_t PHYS_TO_VIRT(uintptr_t paddr); +#endif +#endif \ No newline at end of file diff --git a/um/test/Makefile b/um/test/Makefile new file mode 100644 index 0000000..bb8e572 --- /dev/null +++ b/um/test/Makefile @@ -0,0 +1,35 @@ +# Define the name of your application +APP_NAME = test_app + +# Source files for your application +APP_SRCS = main.cpp +APP_OBJS = $(APP_SRCS:.cpp=.o) + +# Compiler and compiler flags +CXX = g++ +CXXFLAGS = -Wall -Werror -fexceptions + +# Library name and source files +LIB_NAME = libmath_module.so +LIB_SRCS = math_module.cpp +LIB_OBJS = $(LIB_SRCS:.cpp=.o) + +# Additional flags for creating PIC code +PICFLAGS = -fPIC + +# The default target is 'all', which builds your application +all: $(APP_NAME) $(LIB_NAME) + +$(APP_NAME): $(APP_OBJS) $(LIB_NAME) + $(CXX) $(CXXFLAGS) -o $@ $(APP_OBJS) -L. -lmath_module -Wl,-rpath,'$$ORIGIN' + +$(LIB_NAME): $(LIB_OBJS) + $(CXX) -shared -o $@ $(LIB_OBJS) $(CXXFLAGS) + +# Compile source files to object files +%.o: %.cpp + $(CXX) $(CXXFLAGS) $(PICFLAGS) -c $< -o $@ + +# Clean rule to remove the executable, object files, and library +clean: + rm -f $(APP_NAME) $(APP_OBJS) $(LIB_NAME) $(LIB_OBJS) diff --git a/um/test/addr.txt b/um/test/addr.txt new file mode 100644 index 0000000..2f0abf8 --- /dev/null +++ b/um/test/addr.txt @@ -0,0 +1 @@ +0x7fff7203ed94 \ No newline at end of file diff --git a/um/test/main.cpp b/um/test/main.cpp new file mode 100644 index 0000000..396ceb5 --- /dev/null +++ b/um/test/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include "math_module.h" + +int main() { + MathModule math; + + int result = math.add(10, 5); + std::cout << "10 + 5 = " << result << std::endl; + + result = math.subtract(10, 5); + std::cout << "10 - 5 = " << result << std::endl; + + result = math.multiply(10, 5); + std::cout << "10 * 5 = " << result << std::endl; + + double divisionResult = math.divide(10.0, 5.0); + std::cout << "10 / 5 = " << divisionResult << std::endl; + + + int var = 0; + uint64_t* var_virtaddr = reinterpret_cast(&var); + std::ofstream outFile("addr.txt"); + if (!outFile.is_open()) { + std::cerr << "Error: Could not open the file for writing." << std::endl; + return 1; + } + outFile << var_virtaddr; + outFile.close(); + + while (true) { + int random_increment = std::rand() % 100; // Generate a random number between 0 and 99 + std::cout << "before inc value=" << std::dec << var << "@" << std::hex << var_virtaddr << std::endl; + var += random_increment; + std::cout << "after inc value=" << std::dec << var << "@" << std::hex << var_virtaddr << std::endl; + std::cin.get(); // Wait for Enter key press + } + + return 0; +} +//export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. diff --git a/um/test/math_module.cpp b/um/test/math_module.cpp new file mode 100644 index 0000000..7e42771 --- /dev/null +++ b/um/test/math_module.cpp @@ -0,0 +1,24 @@ +#include "math_module.h" + +MathModule::MathModule() { + // Constructor, if any initialization is needed +} + +int MathModule::add(int a, int b) { + return a + b; +} + +int MathModule::subtract(int a, int b) { + return a - b; +} + +int MathModule::multiply(int a, int b) { + return a * b; +} + +double MathModule::divide(double a, double b) { + if (b == 0) { + throw "Division by zero is not allowed."; + } + return a / b; +} diff --git a/um/test/math_module.h b/um/test/math_module.h new file mode 100644 index 0000000..4911b48 --- /dev/null +++ b/um/test/math_module.h @@ -0,0 +1,14 @@ +#ifndef MATH_MODULE_H +#define MATH_MODULE_H + +class MathModule { +public: + MathModule(); // Constructor + + int add(int a, int b); + int subtract(int a, int b); + int multiply(int a, int b); + double divide(double a, double b); +}; + +#endif -- 2.52.0