මෙම සටහන ලියා ඇත්තේ 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.. පද්ධති ඇමතුම් සහ මූලික කාර්යයන් සපයන මෙම පුස්තකාලයයි විවෘත, මැලෝක්, 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, stat යනාදිය, නමුත්, ඇත්ත වශයෙන්ම, ඔවුන් රැවටීමට එම කේත පේළි 5-10 අවශ්ය වේ.
2.2 අපි ඉදිරියට යමු
ඉහත අපි භාවිතා කළා getenv() වටිනාකම ලබා ගැනීමට LD_PRELOAD, නමුත් ලබා ගැනීමට වඩා "පහළ මට්ටමේ" මාර්ගයක් ද ඇත ඊඑන්වී-විචල්ය. අපි අතරමැදි ශ්රිත භාවිතා නොකරමු, නමුත් අරාව වෙත යොමු කරමු **පරිසරය, පරිසරයේ පිටපතක් ගබඩා කරන:
#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;
}
මෙහිදී අපි සෘජුවම මතකයෙන් දත්ත කියවන බැවින්, එවැනි ඇමතුමක් බාධා කළ නොහැකි අතර, අපගේ හඳුනාගන්න_getenv එය තවදුරටත් ආක්රමණය හඳුනා ගැනීමට බාධා නොකරයි.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
මෙම ගැටළුව විසඳී ඇති බව පෙනේද? තාම පටන් ගන්නවා විතරයි.
වැඩසටහන ආරම්භ කිරීමෙන් පසුව, විචල්යයේ අගය LD_PRELOAD හැකර්වරුන්ට එය තවදුරටත් මතකයේ අවශ්ය නොවේ, එනම්, ඕනෑම උපදෙස් ක්රියාත්මක කිරීමට පෙර ඔවුන්ට එය කියවා මකා දැමිය හැකිය. ඇත්ත වශයෙන්ම, මතකයේ අරාවක් සංස්කරණය කිරීම අවම වශයෙන් නරක ක්රමලේඛන විලාසයකි, නමුත් මෙය කෙසේ හෝ අපට සැබවින්ම සුබ පතන්නේ නැති අයෙකු නතර කරන්නේ කෙසේද?
මෙය සිදු කිරීම සඳහා අපි අපගේම ව්යාජ කාර්යයක් නිර්මාණය කළ යුතුය init(), අපි ස්ථාපනය කරන ලද බාධා කිරීම් 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, සහ "NEXT" ලිපින.
#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 Syscalls
මේ සියල්ල බව පෙනේ, නමුත් අපි තවමත් දඟලන්නෙමු. අපි පද්ධති ඇමතුම සෘජුවම කර්නලය වෙත යොමු කරන්නේ නම්, මෙය සම්පූර්ණ බාධා කිරීමේ ක්රියාවලිය මඟ හරිනු ඇත. පහත විසඳුම, ඇත්ත වශයෙන්ම, ගෘහ නිර්මාණ ශිල්පය මත රඳා පවතී (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 යනු මාපිය ක්රියාවලියකට වෙනත් ක්රියාවලියක ප්රගතිය නිරීක්ෂණය කිරීමට සහ පාලනය කිරීමට, එහි දත්ත සහ ලේඛන බැලීමට සහ වෙනස් කිරීමට ඉඩ සලසන මෙවලමකි. සාමාන්යයෙන් මෙම කාර්යය දෝශ නිරාකරණ වැඩසටහනක බිඳුම් ලක්ෂ්ය සෑදීමට සහ පද්ධති ඇමතුම් නිරීක්ෂණය කිරීමට භාවිතා කරයි.
මාපිය ක්රියාවලියට ප්රථමයෙන් fork(2) ඇමතීමෙන් ලුහුබැඳීම ආරම්භ කළ හැකි අතර, පසුව ලැබෙන ළමා ක්රියාවලියට PTRACE_TRACEME ක්රියාත්මක කළ හැක, පසුව (සාමාන්යයෙන්) exec(3) ක්රියාත්මක කිරීමෙන් අනතුරුව. අනෙක් අතට, මාපිය ක්රියාවලියකට PTRACE_ATTACH භාවිතයෙන් පවතින ක්රියාවලියක් නිදොස් කිරීම ආරම්භ කළ හැක.
ලුහුබැඳීමේදී, සංඥාව නොසලකා හරිනු ලැබුවද, සංඥාවක් ලැබෙන සෑම අවස්ථාවකදීම ළමා ක්රියාවලිය නතර වේ. (ව්යතිරේකය සාමාන්යයෙන් ක්රියා කරන SIGKILL වේ.) මාපිය ක්රියාවලියට Wait(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