CTFshow-Pwn入门整数安全(101-110)

pwn101

来学点东西吧

开满了啊,但是是教学关卡应该不会很难,我们直接看看


为我们展示了每种int类型的bytes和range,只有知道这个才能帮助我们溢出
之后让我们输入两个数字,分别存入v4和v5,要求是v4 == 0x80000000 && v5 == 0x7FFFFFFF即可得到flag

实际上按32位有符号int来看,这就是INT_MIN和INT_MAX
所以我们输入-2147483648和2147483647即可

当然了,虽然严格来说输入2147483648会超出范围,但是在这台机子上最后落入变量都是同一个数字,因此都成立

pwn102

保护:

64位,保护几乎全开,应该是教程题

系统提示我输入一个无符号的int,要我们输入的这个无符号int变成-1
简单来说利用的漏洞是同一个4字节内存,按照unsigned输入,又按照signed判断
我输入 4294967295 就能触发,比较的是 cmp eax, 0xffffffff

然而由于scanf的整数转化允许前导符号,-1也会被解析,所以导致-1也能过这个逻辑,回绕一圈

pwn103

保护:

继续反编译查看一下

一开始问我们写入数据的长度,最多选80
后边是__isoc99_scanf(“ %[^\n]”, dest);
会读取直到\n的所有内容放入dest,内容其实无所谓因为后边会被覆盖掉
后边再去从src开始复制v1个字节到dest开始的地方,但是那个地方是null
从null复制字节肯定崩,只有0可以
而且这边检查的是dest的地址值啊,一般的地址值都远远大于114514,本来就会自动满足
因此我们只需要让memcpy那边不崩就好了,我们直接一开始让data只能写0个,那后边memcpy就不会复制去dest,之后就可以通过if触发gift函数了

pwn104

保护:

有什么是可控的,保护关了,不再是那种简单题了,我们来看看如何利用


可以看到这边先问我们要写多少个长度的数据,其实就是不限制我们写入的长度,但是最后read的buf只有0xE,就是一个栈溢出,offset=0xE+8
那我们一开始输入个大一点的就好了,比如30
栈溢出很容易构建

然后利用栈溢出返回到that函数即可
1 | |

这边完全没有用到整数安全,我猜想是我们可以第一次直接发-1,因为是无符号整型,最后会变成一个0xFFFFFFFF,更是容易栈溢出
pwn105

保护:


看看main函数
可以看到这边让我们check一下permissions,然后read的正好把栈填满,但没有栈溢出
我们点到ctfshow()函数看看

发现这边的strcpy更适合栈溢出,只要控制s的内容即可

offset = 0x11+4
然后后门函数也很好找

success = 0x0804870E
所以现在唯一的问题是绕过if语句然后让s传过去
也就是让v3在4-8位之间
注意v3是一个unsigned __int8类型的,所以只能存储0-255的值
256就会变成1,会被截断,而既然是4-8,还要构造栈溢出,那么推荐是溢出的256+4,即260
所以最后用A补齐到260,即0x104长度即可
1 | |

得到flag
pwn106

保护:

题目说还是非常简单,那来看看

定位main函数
上来有个选项,让咱选择去哪,输入的放到v6,输入2就直接exit了,所以得输入1

打开login函数
可以看到这还是没有发现漏洞,继续往下看,有个check_passwd

这就和105很像了,就是一个整数溢出导致dest栈溢出的题目

offset = 0x14+4
这个v3还是保持在4-8位,然后还是strlen,所以就还是260长度的s嘛,(你要261,262什么的也都可以),利用整数溢出确保if成功
前边平稳度过就好了,不会遇到什么问题
栈溢出之后直接跳到获取flag的函数即可

fffflag = 0x08048919
1 | |

pwn107

保护:

只开了NX,用不了shellcode

IDA看看,根据题目说的,这题的重点应该在类型转换,或许就是利用这个atoi将字符串转化为整数

还有一个getch()函数,打开来看看
大概是从标准输入读取字符,然后放到nptr的内存缓冲区
要读多少次呢?那就看第二个参数,是4那就是读4次的,如果遇到空字符或者\n就提前结束

所以这个show函数的逻辑是先让我们输入一个阅读时间次数,利用atoi,它会将字符串解析为整数,包括负号
所以我们写负数进去会全部通过if,还能利用整数溢出写一大堆字节数据
我们只需要写一个-1,能通过if的同时还能在第二步读很多很多

可以看到如果我们输入-1,能直接写4294967295字节的数据

写一个0x2C+4即可
所以栈溢出构造好了,但是这一题还需要注意的是没有线程的后门函数,我们需要自己构造,利用printf来做一个ret2libc即可,这个的细节就不写了,就是32位传参得到libc地址
1 | |

如果发现LibcSearcher跑完还没,那就是和LibcSearcher的版本有关系,我换了个虚拟机就跑出来了,在ctfshow的虚拟机全试了一遍没跑出来。。
(试了一下之前的pwn95也是这个毛病,可能是ctfshow的虚拟机的LibcSearcher太老了,我去整个新的)
pwn108

保护:

?保护全开啊,最好真的只是个游戏

射击小游戏,看着都烦的main函数
1 | |
这边泄露了puts地址,而且是真实地址,不是PIE地址,这明显可以算出libc_base了
之后是v6 = sub_B78()这个关键函数


不断步进查看,可以看到AE3这个函数把我的输入读进了nptr,然后后边又用atol给我转成了一个long整数返回
之后是main函数的两大循环
1 | |
可以看到是三次射击,每次读1字节到v7数组中
之后再把这三个字节写到v6,也就是我指定那个地址的开始的位置,以此部分覆写我指定的地址
由于我只有这一次覆写地址的机会,面对这个全开的东西用ROP明显是不太现实了
我们只有一次机会,自然想到了one_gadget

但在找one_gadget之前需要注意的是,这边还有个过滤黑名单
不允许写0x8c、0xa3、b’\xc5\xf2’和b’”\xf3’
找one_gadget和libc版本有很大关系
1 | |

我们可以动调来看是否满足(注意这边动调会需要我们注意PIE,先来piebase

该用第三个,0x10a2fc
(我不明白为什么我对着同一个libc,跑wp里边那个地址能跑通,跑这仨就跑不通,可是这明明是同一个libc,怎么会这样)
wp是0xe54fe
我们开始写exp,一开始先捕获泄露地址,接下来算libc_base
然后得到one_gadget
之后我们选择改写libc内部的strlen的GOT表(因为dlopen的调用链子很大可能会用到strlen
注意这边程序读地址用的是atol,读的是十进制,所以我们必须发str(strlen_addr),而不是hex(strlen_addr)
最后再循环往v7数组覆盖部分地址即可
最后我们把strlen的GOT表低3字节压成了one_gadget的低3字节,所以在后边dlopen内部走到strlen的时候就读的不再是strlen了,而是直接执行了execve(“/bin/sh”,…)
1 | |

得到flag
pwn109

保护:

开了PIE,但是NX检测情况未知的来了,我们利用execstack试试看

发现是X啊,那是可以执行的,也就是说这一题可以用shellcode
ida打开看看吧,发现很乱

通过logo函数交叉引用可以找到main函数
两个无限循环,然后给了三个选项让我们选一个,一个个看吧,3不用看了,3就是直接break两次出去了
先看1,是输入,一个sub_8A4(buf, 0x400u);

read放到buf,能读nbytes个内容,这边还自己输出了buf的地址

噢正好填满,没有溢出
再看看2吧sub_8E4(buf);

发现是一个格式化字符串,测一下参数位置,注意这边需要先在1里边输入进buf之后再去2看

发现参数在第16位
既然有了shellcode,还有格式化字符串,我可以直接把shellcode写到栈上去运行就好了
理清思路,我们一开始应该先利用泄露的buf地址,先截取得到buf_leak
之后再利用这个得到返回地址

buf = ebp - 0x408
设一下栈对齐之后esp = A,代入ebp = A - 8
所以buf = A - 0x410
又因为又因为A = 原始返回地址 - 0xC
所以原始返回地址ret就是buf + 0x41C
1 | |
之后我们只要把栈的地址写到ret,这样子我们就到了栈上了
之后就只要传入shellcode到这边就可以了,我们在1的buf写,2的漏洞读,之后再回到1的buf写shellcode,最后3跳出retn的时候就成功执行shellcode了
1 | |

ctfshow{1fbec202-9367-47b8-80e1-c371186ab921}
pwn110

保护:

?什么都没开啊,这种题真是不少见了,说明也可以用shellcode
打开IDA看看

主要是这个input函数,步进

(上边这张图我不知道为什么我用ida8.3打开会显示unsigned __int16 v1,很奇怪)
让我们输入应该短整型到v1,接下来检测v1的大小,必须小于等于1024,由于v1是有符号整型,我们还可以写负数来绕过这个
又泄露了buf的地址,然后把int16的v1传给了无符号整型的v3,最后允许写无符号整型v3个字节写入buf
这边就可以利用到整数溢出,我们写-1进v1,又能绕过if,又能得到大的v3使得buf造成栈溢出

可以看见写-1的话直接v3能写65535个字节了
read(0,buf,65535),易构成栈溢出

必须用负数的整数溢出才行,不然原来的1024写满也溢出不了
所以我们已经满足了栈溢出,接下来就和109一样,直接返回到栈地址
然后往栈地址塞shellcode就好了
1 | |
跟这个一样,写exp吧
1 | |

得到flag
整数安全也就告一段落了