我需要解决一个问题,我需要输入一个地址和值来存储在该地址,以便能够劫持一个函数调用。我相信我需要劫持的函数调用是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 string\n"
"vulnerabilities and heap overflows, an adversary can inject arbitrary\n"
"input into critical parts of a process's memory. One such area for\n"
"corruption is the procedure link table: a table of function pointers\n"
"that support dynamically linked library calls. The table is filled in at\n"
"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 memory\n"
"location between 0x0 and 0xff000000 to unlock the program. We have added\n"
"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 of\n"
"the function to execute instead. We have included the source code for you\n"
"to peruse. Note that the password will be read in using:\n"
" scanf(\"%lx \%lx\");\n\n";
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 value\n");
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 %lx\n",(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
使用可执行文件解决问题。
答案 0 :(得分:3)
scanf
+ *ip = i;
可让您在任何可写地址覆盖unsigned long
。看起来代码假设unsigned long
与指针的大小相同,并且它是预期的攻击向量。这在Unix ABI中是典型的,但x86-64 Windows有64位指针和32位long
。
在大多数现代操作系统中,程序文本(包括PLT本身 1 ,在具有一个 2 的可执行文件中)不可写,只能堆栈,数据和BSS。通常在没有exec权限的情况下映射所有这些。因此,通常没有可写的可执行页面,您可以通过野生指针直接修改代码字节。
但GOT(全局偏移表),包括PLT使用的函数指针,保持映射读取+写入,因此惰性动态链接可以在没有mprotect
的情况下工作。有关通过PLT和GOT的间接更多信息,请参阅The Sorry state of dynamic libraries on Linux。
您的网址包含HijackPLT,因此可能是任意覆盖4或8字节的预期攻击媒介。 (顺便说一句,覆盖GOT条目甚至可以用-fno-plt
工作,除非在早期绑定共享库之后将GOT设置为只读。)
使用反汇编程序(如果可执行文件使用ASLR,则使用调试程序单步执行)查找sleep
的GOT条目的地址,并将其作为地址和地址你的目标函数是值。
如果这是32位x86 Linux,则ip < 0xff000000
检查会阻止您修改堆栈内存(通常映射到用户空间内存的顶部,或者位于虚拟地址空间的下半部分的顶部) 。在32位内核上,32位可执行文件的堆栈可能位于0x7f...
,您可以使用此程序 修改它。
脚注1:
IDK,如果历史上PLT本身是可写的并且使用直接跳转而不是间接跳转到从GOT加载的地址。这将略微减少通过PLT的间接开销。它适用于像32位x86这样的架构,其中jmp rel32
可以到达任何目标地址,但不适用于x86-64,其中共享库通常从非PIE可执行文件加载超过2GiB。
但在Arch Linux上由现代gcc7.3制作的Linux i386非PIE 32位可执行文件中的PLT条目如下所示:
# 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本身是可写的,那么你可以重写第二个jmp
,如果你的攻击发生在第一次调用目标之前就进入延迟动态链接解析器库函数。 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
的工作方式类似。