我需要解决一个问题,我需要输入一个地址和值来存储在该地址上,以便能够劫持函数调用。我认为我需要劫持的函数调用是 sleep() 函数。
0x4a7078b5 是正在调用的睡眠函数的地址。 0x4a707776是我想存储在地址中的 print_good() 函数的值。这些是我认为我需要输入才能获得答案的值。但是,当我输入它们时,我没有得到正确的答案。
这是我得到的 c 代码:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
char msg[] =
"(From overthewire.org) When pointers are corrupted from format stringn"
"vulnerabilities and heap overflows, an adversary can inject arbitraryn"
"input into critical parts of a process's memory. One such area forn"
"corruption is the procedure link table: a table of function pointersn"
"that support dynamically linked library calls. The table is filled in atn"
"load time to support run-time code relocation and is often left writeable.n"
"In this level, you are allowed one arbrtrary write to an arbitrary memoryn"
"location between 0x0 and 0xff000000 to unlock the program. We have addedn"
"a call to sleep() that you may hijack. To do so, use objdump" or "gdb"n"
"to find its PLT entry, the memory location to overwrite and the address ofn"
"the function to execute instead. We have included the source code for youn"
"to peruse. Note that the password will be read in using:n"
" scanf("%lx %lx");nn";
void print_good() {
printf("Good Job.n");
exit(0);
}
void segv_handler(int sig) {
printf("Segmentation fault. Try again.n");
exit(0);
}
void ill_handler(int sig) {
printf("Illegal instruction hit. Try again.n");
exit(0);
}
void print_msg() {
printf("%s",msg);
}
int main()
{
unsigned long int *ip;
unsigned long int i;
signal(SIGSEGV, segv_handler);
signal(SIGILL, ill_handler);
print_msg();
printf("The password is a hexadecimal address and a hexadecimal valuen");
printf("to place at that address.n");
printf("Enter the password: ");
scanf("%lx %lx",(unsigned long int *) &ip,&i);
if (ip > (unsigned long int *) 0xff000000) {
printf("Address too high. Try again.n");
exit(0);
}
*ip = i;
printf("The address: %lx will now contain %lxn",(unsigned long int) ip,i);
sleep(1);
printf("Try again.n");
exit(0);
}
我还得到了这个 c 代码的可执行文件。
如果您想自己动手来帮助我,这里有可执行文件可以做到这一点: http://www.mediafire.com/file/bnqri8my95zqwmz/Ch3_07_HijackPLT/file
这是 c 代码: http://www.mediafire.com/file/3lrty8b028te3d2/Ch3_07_HijackPLT.c/file
使用可执行文件来解决问题。
scanf
+*ip = i;
允许您覆盖任何可写地址的unsigned long
。 看起来代码假设unsigned long
与指针的大小相同,并且这是预期的攻击媒介。 这在Unix ABI中很常见,但是x86-64 Windows具有64位指针和32位long
。
在大多数现代操作系统中,程序文本(包括PLT本身1,在具有2的可执行文件中)将不可写,只能写入堆栈,数据和BSS。 所有这些通常在没有执行许可的情况下进行映射。 因此,通常没有可写 + 可执行页面,您可以在其中直接通过百搭指针修改代码字节。
但是GOT(全局偏移表),包括PLT使用的函数指针,保持映射的读+写,因此懒惰的动态链接可以在没有mprotect
的情况下工作。 请参阅 Linux 上动态库的抱歉状态,了解有关通过 PLT 和 GOT 进行间接寻址的更多信息。
您的URL包含HijackPLT,因此大概这是任意覆盖4或8个字节的预期攻击媒介。 (顺便说一句,覆盖GOT条目即使使用-fno-plt
也可以,除非在早期绑定共享库后将GOT变为只读。
使用反汇编器(如果可执行文件使用 ASLR,则使用调试器的单步)找出 GOT 条目的地址sleep
,并将其作为地址,目标函数的地址作为值。
如果这是 32 位 x86 Linux,则ip < 0xff000000
检查会阻止您修改堆栈内存(通常映射在用户空间内存的顶部,或虚拟地址空间的下半部分的顶部)。 在 32 位内核上,32 位可执行文件的堆栈可能位于0x7f...
,您可以使用此程序对其进行修改。
脚注1:
IDK,如果历史上PLT本身是可写的,并且使用直接跳转而不是间接跳转到从GOT加载的地址。 这将略微减少通过PLT间接间接产生的开销。 它适用于 32 位 x86 等架构,其中jmp rel32
可以到达任何目标地址,但不适用于 x86-64,其中共享库通常从非 PIE 可执行文件加载超过 2GiB。
但是Linux i386非PIE 32位可执行文件中的PLT条目由Arch Linux上的现代gcc7.3制作,如下所示:
# from objdump -drwC -Mintel on an executable from gcc -m32 -fno-pie -no-pie
08048350 <puts@plt>:
8048350: ff 25 0c a0 04 08 jmp DWORD PTR ds:0x804a00c
8048356: 68 00 00 00 00 push 0x0
804835b: e9 e0 ff ff ff jmp 8048340 <.plt>
在第一次调用puts@plt
时,间接jmp
加载一个跳转目标,该目标将其带到push
指令,jmp .plt
将其带到调用动态链接器的符号解析器的代码(将0
作为要解析的符号条目的函数 arg)。
完成后,它会更新 GOT 条目,以便将来对此 PLT 条目的调用将直接jmp
到 libc 中的puts
。 然后它跳到那里,所以这个调用在返回到只想call puts
的代码之前运行实际函数。
如果 PLT 本身是可写的,您可以重写第 2jmp
,即进入惰性动态链接解析器的那个,如果您的攻击发生在第一次调用目标库函数之前。 GOT条目永远不会更新,因此它会继续跳转到下一条指令。
或者,在具有可写 PLT 的 x86-64 上,您有 8 个字节unsigned long
因此您的写入可以包含完整的 5 字节jmp rel32
指令,将间接 jmp 替换为直接 jmp 到您想要的功能。 但这有点愚蠢,因为PLT是文本段的一部分,除非您使用奇怪的选项进行编译,否则无法映射可写。 只需覆盖 GOT 条目;无论如何,当您完全控制覆盖的地址和数据时,这更容易。
脚注2:并非所有可执行文件都有PLT:仅在动态链接的可执行文件中,通常仅在类Unix操作系统上。 即使在传统上使用PLT调用共享库中函数的平台上,-fno-plt
也会内联内存间接call [sleep@got]
指令(x86示例),对PIC代码中的GOT条目使用PC相对寻址,或者如果这是解决静态数据的最有效方法(例如x86-64)。
Windows对DLL中函数的调用的工作方式类似于gcc -fno-plt
。