ఈ గమనిక 2014లో వ్రాయబడింది, కానీ నేను హబ్రేపై అణచివేతకు గురయ్యాను మరియు అది వెలుగు చూడలేదు. నిషేధం సమయంలో నేను దాని గురించి మరచిపోయాను, కానీ ఇప్పుడు నేను దానిని డ్రాఫ్ట్లలో కనుగొన్నాను. నేను దానిని తొలగించడం గురించి ఆలోచించాను, కానీ అది ఎవరికైనా ఉపయోగకరంగా ఉండవచ్చు.
సాధారణంగా, "చేర్చబడినవి" కోసం శోధించే అంశంపై కొద్దిగా శుక్రవారం అడ్మిన్ చదవడం LD_PRELOAD.
1. ఫంక్షన్ ప్రత్యామ్నాయం గురించి తెలియని వారి కోసం ఒక చిన్న డైగ్రెషన్
మిగిలినవి నేరుగా వెళ్లవచ్చు అంశం 2.
ఒక క్లాసిక్ ఉదాహరణతో ప్రారంభిద్దాం:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand (time(NULL));
for(int i=0; i<5; i++){
printf ("%dn", rand()%100);
}
}
మేము ఎటువంటి జెండాలు లేకుండా కంపైల్ చేస్తాము:
$ gcc ./ld_rand.c -o ld_rand
మరియు, ఊహించిన విధంగా, మేము 5 కంటే తక్కువ 100 యాదృచ్ఛిక సంఖ్యలను పొందుతాము:
$ ./ld_rand
53
93
48
57
20
కానీ మనకు ప్రోగ్రామ్ యొక్క సోర్స్ కోడ్ లేదని ఊహించుకుందాం, కానీ మనం ప్రవర్తనను మార్చుకోవాలి.
మన స్వంత ఫంక్షన్ ప్రోటోటైప్తో మన స్వంత లైబ్రరీని క్రియేట్ చేద్దాం, ఉదాహరణకు:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
మరియు ఇప్పుడు మా యాదృచ్ఛిక ఎంపిక చాలా ఊహించదగినది:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
మేము ముందుగా మా లైబ్రరీని దీని ద్వారా ఎగుమతి చేస్తే ఈ ట్రిక్ మరింత ఆకర్షణీయంగా కనిపిస్తుంది
$ export LD_PRELOAD=$PWD/ld_rand.so
లేదా మేము మొదట చేస్తాము
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
ఆపై ప్రోగ్రామ్ను యథావిధిగా అమలు చేయండి. మేము ప్రోగ్రామ్లోనే ఒక్క లైన్ కోడ్ను మార్చలేదు, కానీ దాని ప్రవర్తన ఇప్పుడు మా లైబ్రరీలోని ఒక చిన్న ఫంక్షన్పై ఆధారపడి ఉంటుంది. అంతేకాకుండా, ప్రోగ్రామ్ వ్రాసే సమయంలో, ది ర్యాండ్ ఉనికిలో కూడా లేదు.
మా ప్రోగ్రామ్ నకిలీని ఉపయోగించేలా చేసింది ర్యాండ్? దశలవారీగా చేద్దాం.
అప్లికేషన్ ప్రారంభించినప్పుడు, ప్రోగ్రామ్కు అవసరమైన ఫంక్షన్లను కలిగి ఉన్న నిర్దిష్ట లైబ్రరీలు లోడ్ చేయబడతాయి. వాటిని ఉపయోగించి మనం చూడవచ్చు ldd:
# ldd ./ld_rand
linux-vdso.so.1 (0x00007ffc8b1f3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe3da8af000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe3daa7e000)
OS సంస్కరణను బట్టి ఈ జాబితా మారవచ్చు, కానీ అక్కడ తప్పనిసరిగా ఫైల్ ఉండాలి libc.so.. ఈ లైబ్రరీ సిస్టమ్ కాల్లు మరియు వంటి ప్రాథమిక విధులను అందిస్తుంది ఓపెన్, malloc, printf మొదలైనవి మా ర్యాండ్ వాటిలో కూడా ఉంది. దీన్ని నిర్ధారించుకుందాం:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
ఉపయోగించినప్పుడు లైబ్రరీల సెట్ మారుతుందో లేదో చూద్దాం LD_PRELOAD
# LD_PRELOAD=$PWD/ld_rand.so ldd ./ld_rand
linux-vdso.so.1 (0x00007ffea52ae000)
/scripts/c/ldpreload/ld_rand.so (0x00007f690d3f9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f690d230000)
/lib64/ld-linux-x86-64.so.2 (0x00007f690d405000)
వేరియబుల్ సెట్ చేయబడిందని తేలింది LD_PRELOAD మనల్ని లోడ్ చేయమని బలవంతం చేస్తుంది ld_rand.so కార్యక్రమం కూడా అవసరం లేదు వాస్తవం ఉన్నప్పటికీ. మరియు మా ఫంక్షన్ నుండి "రాండ్" కంటే ముందుగా లోడ్ అవుతుంది ర్యాండ్ от libc.so., అప్పుడు ఆమె రూస్ట్ను పాలిస్తుంది.
సరే, మేము స్థానిక ఫంక్షన్ను భర్తీ చేయగలిగాము, అయితే దాని కార్యాచరణ సంరక్షించబడిందని మరియు కొన్ని చర్యలు జోడించబడిందని మేము ఎలా నిర్ధారించుకోవచ్చు. మన యాదృచ్ఛికాన్ని సవరించుకుందాం:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_rand_f_type)(void);
int rand()
{
/* Выполняем некий код */
printf("Evil injected coden");
orig_rand_f_type orig_rand;
orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT,"rand");
return orig_rand();
}
ఇక్కడ, మా “అదనంగా”, మేము ఒక లైన్ టెక్స్ట్ను మాత్రమే ప్రింట్ చేస్తాము, దాని తర్వాత మేము అసలు ఫంక్షన్కు పాయింటర్ను సృష్టిస్తాము ర్యాండ్. ఈ ఫంక్షన్ యొక్క చిరునామాను పొందడానికి మనకు అవసరం dlsym లైబ్రరీ నుండి ఒక ఫంక్షన్ libdlఇది మా కనుగొంటుంది ర్యాండ్ డైనమిక్ లైబ్రరీల స్టాక్లో. దాని తర్వాత మేము ఈ ఫంక్షన్ని పిలుస్తాము మరియు దాని విలువను తిరిగి ఇస్తాము. దీని ప్రకారం, మేము జోడించాలి "-ldl" అసెంబ్లీ సమయంలో:
$ gcc -ldl -shared -fPIC ./o_rand_evil.c -o ld_rand_evil.so
$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Evil injected code
66
Evil injected code
28
Evil injected code
93
Evil injected code
93
Evil injected code
95
మరియు మా ప్రోగ్రామ్ "స్థానికం"ని ఉపయోగిస్తుంది ర్యాండ్, గతంలో కొన్ని అశ్లీల చర్యలను కలిగి ఉండటం.
2. శోధన యొక్క నొప్పి
సంభావ్య ముప్పు గురించి తెలుసుకోవడం, మేము దానిని గుర్తించాలనుకుంటున్నాము ప్రీలోడ్ ఇది ప్రదర్శించబడింది. గుర్తించడానికి ఉత్తమ మార్గం దానిని కెర్నల్లోకి నెట్టడం అని స్పష్టంగా ఉంది, అయితే నేను యూజర్స్పేస్లోని డిటెక్షన్ ఎంపికలపై ఆసక్తి కలిగి ఉన్నాను.
తరువాత, గుర్తించడం మరియు వాటి ఖండన కోసం పరిష్కారాలు జంటగా వస్తాయి.
2.1 సరళంగా ప్రారంభిద్దాం
ముందే చెప్పినట్లుగా, మీరు వేరియబుల్ని ఉపయోగించి లైబ్రరీని లోడ్ చేయడానికి పేర్కొనవచ్చు LD_PRELOAD లేదా ఫైల్లో వ్రాయడం ద్వారా /etc/ld.so.preload. రెండు సాధారణ డిటెక్టర్లను సృష్టిద్దాం.
మొదటిది సెట్ ఎన్విరాన్మెంట్ వేరియబుల్ని తనిఖీ చేయడం:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char* pGetenv = getenv("LD_PRELOAD");
pGetenv != NULL ?
printf("LD_PRELOAD (getenv) [+]n"):
printf("LD_PRELOAD (getenv) [-]n");
}
రెండవది ఫైల్ తెరవబడిందో లేదో తనిఖీ చేయడం:
#include <stdio.h>
#include <fcntl.h>
int main()
{
open("/etc/ld.so.preload", O_RDONLY) != -1 ?
printf("LD_PRELOAD (open) [+]n"):
printf("LD_PRELOAD (open) [-]n");
}
లైబ్రరీలను లోడ్ చేద్దాం:
$ export LD_PRELOAD=$PWD/ld_rand.so
$ echo "$PWD/ld_rand.so" > /etc/ld.so.preload
$ ./detect_base_getenv
LD_PRELOAD (getenv) [+]
$ ./detect_base_open
LD_PRELOAD (open) [+]
ఇక్కడ మరియు దిగువన, [+] విజయవంతమైన గుర్తింపును సూచిస్తుంది.
దీని ప్రకారం, [-] అంటే బైపాస్ డిటెక్షన్.
అటువంటి డిటెక్టర్ ఎంత ప్రభావవంతంగా ఉంటుంది? ముందుగా పర్యావరణ వేరియబుల్ని పరిశీలిద్దాం:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
char* (*orig_getenv)(const char *) = NULL;
char* getenv(const char *name)
{
if(!orig_getenv) orig_getenv = dlsym(RTLD_NEXT, "getenv");
if(strcmp(name, "LD_PRELOAD") == 0) return NULL;
return orig_getenv(name);
}
$ gcc -shared -fpic -ldl ./ld_undetect_getenv.c -o ./ld_undetect_getenv.so
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_base_getenv
LD_PRELOAD (getenv) [-]
అదేవిధంగా, మేము చెక్కును వదిలించుకుంటాము ఓపెన్:
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
int (*orig_open)(const char*, int oflag) = NULL;
int open(const char *path, int oflag, ...)
{
char real_path[256];
if(!orig_open) orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0){
errno = ENOENT;
return -1;
}
return orig_open(path, oflag);
}
$ gcc -shared -fpic -ldl ./ld_undetect_open.c -o ./ld_undetect_open.so
$ LD_PRELOAD=./ld_undetect_open.so ./detect_base_open
LD_PRELOAD (open) [-]
అవును, ఫైల్ని యాక్సెస్ చేయడానికి ఇతర మార్గాలను ఇక్కడ ఉపయోగించవచ్చు, ఉదాహరణకు, తెరవండి64, రాష్ట్ర మొదలైనవి, కానీ, నిజానికి, వాటిని మోసగించడానికి అదే 5-10 లైన్ల కోడ్ అవసరం.
2.2 ముందుకు వెళ్దాం
పైన మేము ఉపయోగించాము getenv() విలువ పొందడానికి LD_PRELOAD, కానీ చేరుకోవడానికి మరింత "తక్కువ-స్థాయి" మార్గం కూడా ఉంది ENV-వేరియబుల్స్. మేము ఇంటర్మీడియట్ ఫంక్షన్లను ఉపయోగించము, కానీ శ్రేణిని సూచిస్తాము ** పర్యావరణం, ఇది పర్యావరణం యొక్క కాపీని నిల్వ చేస్తుంది:
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char **argv) {
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
{
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL)
{
printf("LD_PRELOAD (**environ) [+]n");
return 0;
}
}
printf("LD_PRELOAD (**environ) [-]n");
return 0;
}
ఇక్కడ మేము మెమరీ నుండి నేరుగా డేటాను చదువుతున్నాము కాబట్టి, అటువంటి కాల్ని అడ్డగించలేము మరియు మా గుర్తించు_గెటెన్వ్ ఇది చొరబాట్లను గుర్తించడంలో జోక్యం చేసుకోదు.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
ఈ సమస్య పరిష్కరించబడినట్లు అనిపిస్తుంది? ఇది ఇంకా ప్రారంభం అవుతోంది.
ప్రోగ్రామ్ ప్రారంభించిన తర్వాత, వేరియబుల్ విలువ LD_PRELOAD హ్యాకర్లకు మెమరీలో ఇది అవసరం లేదు, అంటే, వారు ఏదైనా సూచనలను అమలు చేయడానికి ముందు దాన్ని చదివి, తొలగించవచ్చు. అయితే, మెమరీలో శ్రేణిని సవరించడం అనేది కనీసం ఒక చెడ్డ ప్రోగ్రామింగ్ శైలి, అయితే ఇది నిజంగా మనకు మంచి జరగాలని కోరుకోని వారిని ఆపగలదా?
దీన్ని చేయడానికి, మన స్వంత నకిలీ ఫంక్షన్ను సృష్టించాలి అందులో(), దీనిలో మేము ఇన్స్టాల్ చేసిన వాటిని అడ్డగిస్తాము LD_PRELOAD మరియు దానిని మా లింకర్కు పంపండి:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
extern char **environ;
char *evil_env;
int (*orig_execve)(const char *path, char *const argv[], char *const envp[]) = NULL;
// Создаём фейковую версию init
// которая будет вызвана при загрузке программы
// до выполнения каких-либо инструкций
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
unsetenv(env);
break;
}
}
}
int execve(const char *path, char *const argv[], char *const envp[])
{
int i = 0, j = 0, k = -1, ret = 0;
char** new_env;
if(!orig_execve) orig_execve = dlsym(RTLD_NEXT,"execve");
// Проверям не существует ли других установленных LD_PRELOAD
for(i = 0; envp[i]; i++){
if(strstr(envp[i], "LD_PRELOAD")) k = i;
}
// Если LD_PRELOAD не было установлено до нас, то добавим его
if(k == -1){
k = i;
i++;
}
// Создаём новое окружение
new_env = (char**) malloc((i+1)*sizeof(char*));
// Копируем старое окружение, за исключением LD_PRELOAD
for(j = 0; j < i; j++) {
// перезаписываем или создаём LD_PRELOAD
if(j == k) {
new_env[j] = (char*) malloc(256);
strcpy(new_env[j], "LD_PRELOAD=");
strcat(new_env[j], evil_env);
}
else new_env[j] = (char*) envp[j];
}
new_env[i] = NULL;
ret = orig_execve(path, argv, new_env);
free(new_env[k]);
free(new_env);
return ret;
}
మేము అమలు చేస్తాము మరియు తనిఖీ చేస్తాము:
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ.c -o ./ld_undetect_environ.so
$ LD_PRELOAD=./ld_undetect_environ.so ./detect_environ
LD_PRELOAD (**environ) [-]
2.3 /proc/self/
అయితే, మీరు ప్రత్యామ్నాయాన్ని కనుగొనగలిగే చివరి స్థలం మెమరీ కాదు LD_PRELOAD, కూడా ఉంది /proc/. స్పష్టమైన తో ప్రారంభిద్దాం /proc/{PID}/ఎన్విరాన్.
వాస్తవానికి, దీనికి సార్వత్రిక పరిష్కారం ఉంది గుర్తించు ** పర్యావరణం и /proc/self/environ. సమస్య "తప్పు" ప్రవర్తన unsetenv(env).
సరైన ఎంపిక
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
//unsetenv(env);
// Вместо unset просто обнулим нашу переменную
for(int j = 0; environ[i][j] != ' '; j++) environ[i][j] = ' ';
break;
}
}
}
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ_2.c -o ./ld_undetect_environ_2.so
$ (LD_PRELOAD=./ld_undetect_environ_2.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
కానీ మేము దానిని కనుగొనలేదని ఊహించుకుందాం మరియు /proc/self/environ "సమస్యాత్మక" డేటాను కలిగి ఉంది.
ముందుగా మన మునుపటి "వేషధారణ"తో ప్రయత్నిద్దాం:
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
పిల్లి ఫైల్ని తెరవడానికి అదే ఉపయోగిస్తుంది ఓపెన్(), కాబట్టి పరిష్కారం సెక్షన్ 2.1లో ఇప్పటికే చేసిన దానితో సమానంగా ఉంటుంది, కానీ ఇప్పుడు మేము ఒక తాత్కాలిక ఫైల్ను సృష్టిస్తాము, అక్కడ పంక్తులు లేకుండా నిజమైన మెమరీ విలువలను కాపీ చేస్తాము LD_PRELOAD.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#define BUFFER_SIZE 256
int (*orig_open)(const char*, int oflag) = NULL;
char *soname = "fakememory_preload.so";
char *sstrstr(char *str, const char *sub)
{
int i, found;
char *ptr;
found = 0;
for(ptr = str; *ptr != ' '; ptr++) {
found = 1;
for(i = 0; found == 1 && sub[i] != ' '; i++){
if(sub[i] != ptr[i]) found = 0;
}
if(found == 1)
break;
}
if(found == 0)
return NULL;
return ptr + i;
}
void fakeMaps(char *original_path, char *fake_path, char *pattern)
{
int fd;
char buffer[BUFFER_SIZE];
int bytes = -1;
int wbytes = -1;
int k = 0;
pid_t pid = getpid();
int fh;
if ((fh=orig_open(fake_path,O_CREAT|O_WRONLY))==-1) {
printf("LD: Cannot open write-file [%s] (%d) (%s)n", fake_path, errno, strerror(errno));
exit (42);
}
if((fd=orig_open(original_path, O_RDONLY))==-1) {
printf("LD: Cannot open read-file.n");
exit(42);
}
do
{
char t = 0;
bytes = read(fd, &t, 1);
buffer[k++] = t;
//printf("%c", t);
if(t == ' ') {
//printf("n");
if(!sstrstr(buffer, "LD_PRELOAD")) {
if((wbytes = write(fh,buffer,k))==-1) {
//printf("write errorn");
}
else {
//printf("writed %dn", wbytes);
}
}
k = 0;
}
}
while(bytes != 0);
close(fd);
close(fh);
}
int open(const char *path, int oflag, ...)
{
char real_path[PATH_MAX], proc_path[PATH_MAX], proc_path_0[PATH_MAX];
pid_t pid = getpid();
if(!orig_open)
orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
snprintf(proc_path, PATH_MAX, "/proc/%d/environ", pid);
if(strcmp(real_path, proc_path) == 0) {
snprintf(proc_path, PATH_MAX, "/tmp/%d.fakemaps", pid);
realpath(proc_path_0, proc_path);
fakeMaps(real_path, proc_path, soname);
return orig_open(proc_path, oflag);
}
return orig_open(path, oflag);
}
మరియు ఈ దశ దాటింది:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
తదుపరి స్పష్టమైన స్థలం /proc/self/maps. దాని గురించి ఆలోచించడం వల్ల ప్రయోజనం లేదు. పరిష్కారం మునుపటిదానికి ఖచ్చితంగా సమానంగా ఉంటుంది: ఫైల్ నుండి డేటాను మైనస్ మధ్య లైన్లను కాపీ చేయండి libc.so. и ld.so..
2.4 Chokepoint నుండి ఎంపిక
నేను ఈ పరిష్కారాన్ని దాని సరళత కోసం ప్రత్యేకంగా ఇష్టపడ్డాను. మేము నేరుగా లోడ్ చేయబడిన ఫంక్షన్ల చిరునామాలను సరిపోల్చాము libc, మరియు "తదుపరి" చిరునామాలు.
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"
int main(int argc, char *argv[]) {
void *libc = dlopen(LIBC, RTLD_LAZY); // Open up libc directly
char *syscall_open = "open";
int i;
void *(*libc_func)();
void *(*next_func)();
libc_func = dlsym(libc, syscall_open);
next_func = dlsym(RTLD_NEXT, syscall_open);
if (libc_func != next_func) {
printf("LD_PRELOAD (syscall - %s) [+]n", syscall_open);
printf("Libc address: %pn", libc_func);
printf("Next address: %pn", next_func);
}
else {
printf("LD_PRELOAD (syscall - %s) [-]n", syscall_open);
}
return 0;
}
అంతరాయంతో లైబ్రరీని లోడ్ చేస్తోంది "ఓపెన్()" మరియు తనిఖీ చేయండి:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
తిరస్కరణ మరింత సరళంగా మారింది:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
extern void * _dl_sym (void *, const char *, void *);
void * dlsym (void * handle, const char * symbol)
{
return _dl_sym (handle, symbol, dlsym);
}
# LD_PRELOAD=./ld_undetect_chokepoint.so ./detect_chokepoint
LD_PRELOAD (syscall - open) [-]
2.5 సిస్కాల్స్
ఇది అంతా అని అనిపించవచ్చు, కాని మేము ఇంకా తడబడతాము. మేము సిస్టమ్ కాల్ను నేరుగా కెర్నల్కు మళ్లిస్తే, ఇది మొత్తం అంతరాయ ప్రక్రియను దాటవేస్తుంది. దిగువ పరిష్కారం, వాస్తవానికి, నిర్మాణంపై ఆధారపడి ఉంటుంది (x86_64) ఓపెనింగ్ను గుర్తించడానికి దాన్ని అమలు చేయడానికి ప్రయత్నిద్దాం ld.so.preload.
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFER_SIZE 256
int syscall_open(char *path, long oflag)
{
int fd = -1;
__asm__ (
"mov $2, %%rax;" // Open syscall number
"mov %1, %%rdi;" // Address of our string
"mov %2, %%rsi;" // Open mode
"mov $0, %%rdx;" // No create mode
"syscall;" // Straight to ring0
"mov %%eax, %0;" // Returned file descriptor
:"=r" (fd)
:"m" (path), "m" (oflag)
:"rax", "rdi", "rsi", "rdx"
);
return fd;
}
int main()
{
syscall_open("/etc/ld.so.preload", O_RDONLY) > 0 ?
printf("LD_PRELOAD (open syscall) [+]n"):
printf("LD_PRELOAD (open syscall) [-]n");
}
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
మరియు ఈ సమస్యకు పరిష్కారం ఉంది. నుండి సారాంశం మనిషి'జ:
ptrace అనేది పేరెంట్ ప్రాసెస్ను మరొక ప్రక్రియ యొక్క పురోగతిని గమనించడానికి మరియు నియంత్రించడానికి, దాని డేటా మరియు రిజిస్టర్లను వీక్షించడానికి మరియు మార్చడానికి అనుమతించే సాధనం. సాధారణంగా ఈ ఫంక్షన్ డీబగ్గింగ్ ప్రోగ్రామ్లో బ్రేక్పాయింట్లను సృష్టించడానికి మరియు సిస్టమ్ కాల్లను పర్యవేక్షించడానికి ఉపయోగించబడుతుంది.
పేరెంట్ ప్రాసెస్ ముందుగా ఫోర్క్(2)కి కాల్ చేయడం ద్వారా ట్రేసింగ్ను ప్రారంభించవచ్చు, ఆపై ఫలితంగా వచ్చే చైల్డ్ ప్రాసెస్ PTRACE_TRACEMEని అమలు చేయగలదు, తర్వాత (సాధారణంగా) exec(3)ని అమలు చేయడం ద్వారా. మరోవైపు, పేరెంట్ ప్రాసెస్ PTRACE_ATTACHని ఉపయోగించి ఇప్పటికే ఉన్న ప్రాసెస్ని డీబగ్ చేయడాన్ని ప్రారంభించవచ్చు.
ట్రేస్ చేస్తున్నప్పుడు, సిగ్నల్ విస్మరించబడినప్పటికీ, సిగ్నల్ అందుకున్న ప్రతిసారి చైల్డ్ ప్రాసెస్ ఆగిపోతుంది. (మినహాయింపు SIGKILL, ఇది సాధారణంగా పని చేస్తుంది.) పేరెంట్ ప్రాసెస్కి వెయిట్(2) కాల్ చేయడం ద్వారా దీని గురించి తెలియజేయబడుతుంది, ఆ తర్వాత అది ప్రారంభమయ్యే ముందు చైల్డ్ ప్రాసెస్లోని కంటెంట్లను వీక్షించగలదు మరియు సవరించగలదు. పేరెంట్ ప్రాసెస్ పిల్లవాడిని పరుగు కొనసాగించడానికి అనుమతిస్తుంది, కొన్ని సందర్భాల్లో దానికి పంపిన సిగ్నల్ను విస్మరించడం లేదా బదులుగా మరొక సిగ్నల్ పంపడం).
అందువల్ల, ప్రక్రియను పర్యవేక్షించడం, ప్రతి సిస్టమ్ కాల్కు ముందు దాన్ని ఆపడం మరియు అవసరమైతే, థ్రెడ్ను హుక్ ఫంక్షన్కి మళ్లించడం పరిష్కారం.
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <asm/unistd.h>
#if defined(__x86_64__)
#define REG_SYSCALL ORIG_RAX
#define REG_SP rsp
#define REG_IP rip
#endif
long NOHOOK = 0;
long evil_open(const char *path, long oflag, long cflag)
{
char real_path[PATH_MAX], maps_path[PATH_MAX];
long ret;
pid_t pid;
pid = getpid();
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0)
{
errno = ENOENT;
ret = -1;
}
else
{
NOHOOK = 1; // Entering NOHOOK section
ret = open(path, oflag, cflag);
}
// Exiting NOHOOK section
NOHOOK = 0;
return ret;
}
void init()
{
pid_t program;
// Форкаем дочерний процесс
program = fork();
if(program != 0) {
int status;
long syscall_nr;
struct user_regs_struct regs;
// Подключаемся к дочернему процессу
if(ptrace(PTRACE_ATTACH, program) != 0) {
printf("Failed to attach to the program.n");
exit(1);
}
waitpid(program, &status, 0);
// Отслеживаем только SYSCALLs
ptrace(PTRACE_SETOPTIONS, program, 0, PTRACE_O_TRACESYSGOOD);
while(1) {
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
if(WIFEXITED(status) || WIFSIGNALED(status)) break;
else if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP|0x80) {
// Получаем номер системного вызова
syscall_nr = ptrace(PTRACE_PEEKUSER, program, sizeof(long)*REG_SYSCALL);
if(syscall_nr == __NR_open) {
// Читаем слово из памяти дочернего процесса
NOHOOK = ptrace(PTRACE_PEEKDATA, program, (void*)&NOHOOK);
// Перехватываем вызов
if(!NOHOOK) {
// Копируем регистры дочернего процесса
// в переменную regs родительского
ptrace(PTRACE_GETREGS, program, 0, ®s);
// Push return address on the stack
regs.REG_SP -= sizeof(long);
// Копируем слово в память дочернего процесса
ptrace(PTRACE_POKEDATA, program, (void*)regs.REG_SP, regs.REG_IP);
// Устанавливаем RIP по адресу evil_open
regs.REG_IP = (unsigned long) evil_open;
// Записываем состояние регистров процесса
ptrace(PTRACE_SETREGS, program, 0, ®s);
}
}
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
}
}
exit(0);
}
else {
sleep(0);
}
}
మేము తనిఖీ చేస్తాము:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
చాలా ధన్యవాదాలు
, ఈ గమనికను ఇక్కడ కనిపించేలా చేయడానికి నేను చేసిన దానికంటే వీరి కథనాలు, మూలాలు మరియు వ్యాఖ్యలు చాలా ఎక్కువ చేశాయి.
మూలం: www.habr.com