没啥好说的,淦就完事了。
WEB
Judge
太暴力啦!出题的学长列入十一月加急名单哈。
1 | import requests, re, time |
注意要用 s = requests.Session()
保留通话记录(cookie)
Hello_PHP
http://202.119.201.199:50003/index.php?a=&b=
PUT me a message
1 | import requests |
没搞明白为什么put传的不是key: value形式的数据。
RE
Hello World
注意端序
easy XOR
1 | v8 = ':\"AL_RT^L*.?+6/46' |
v7,注意端序哈。
答案就在眼前
change()的作用是将CUMTCTF{this_is_a_fake_flag_
分割成['this', '_is_', 'a_fa', 'ke_f', 'lag_']
,并分别转换成int
然后读入5个int,分别比较。
1 | def hex2char(data): |
flag就是输入去掉\n
another XOR
1 | a = [0x0a,0x75,0x2c,0x39,0x63,0x20,0x2f,0x9,0x1d,0x0b,0x3b,0x7f,0x08,0x1e,0x7f,0x15,0x1e,0x17,0x16,0x0b,0x24,0x45,0x1c] |
string
1 | s = '{opzfpzfzvfzptwsl' |
Mind reading
随机数种子是一个常值,所以随机数序列是固定的。
动态调试轻松找到v2的值
X0
关键代码:
( v1[*(char *)(i + a1) ^ *((_DWORD *)&v4 + i)] != v2[i] )
即v1[ a1[i] ^ v4[i] ] == v2[i]
,求a1
1 | v2 = '000ldh_tphhl' |
选取有意义的组合。
MISC
包包包
添加后缀.zip
签到都算不上
查看文件属性
结账拿出什么?
base64编码的图片。markdown语法轻松看到图片。
forfun
forfuntoo
按照提示,字母要小写提交
PWN
0x00 写在前面
刚开始学pwn,有些地方可能理解的不对,有错误的话希望大家可以指出来,谢谢。
使用到了**python2 **的一个模块pwn(pip时Ubuntu下是pwn、CentOS7中是pwntools),不能在windows下使用。
python3应该也可以使用pwntools,但我还没有实践过,这里不详细说。
我的环境为Ubuntu16.04,python2.
1 | #Ubuntu下安装pwn |
Ubuntu比较适合pwntools,CentOS的坑比较多。推荐Ubuntu哈
来自pwnht的评论(闲话/补充?????)
ubuntu安装的时候也可以安装pwntools
1 | pip install pwntools |
我之前都不知道有pwn这个库,但是用法是一样的,却是两个库,额,建议pwntools哈,可以从下面来,pwn这个最后一次更新在2014,好像已经停止更新了,而pwntools最后一次更新是在2019(一看 pwntools 的 decription 就比较帅,知道选谁了吧)
0x01 first try
32位,用ida打开,按下f5将汇编转换成伪C代码。
来自pwnht的评论(闲话/补充?????)
理论上来说,我们第一步是checksec,一般pwn题目是不加壳的,但是,会有一些程序保护(详情参考的我ppt程序保护部分),幸运的是这几道题目都没保护2333,其实,checksec是有一个脚本的,但是我更喜欢用ipython这个程序,安装ipython
1 | sudo apt install ipython |
像这样操作,linux里面arch表示程序的是哪种操作系统类型(i386为32位,amd64为64位),然后下面是4种保护,红色表示程序保护没开,绿色表示开启
(其实ida也能分辨64位和32位试一试就知道会有标识的)
(下面是分割线)
要想拿到shell,就要满足v6=99(第30行)。
本题存在两处输入,其中第11行的read()函数存在明显的栈溢出。
双击程序中的任意参数或局部变量,均可跳转至所在函数的栈界面:
容易发现,s字符串的长度为0x1C-0x0C=16,而read()函数可以读入50个字符。当读完16个字符后,如果继续输入,程序并不会停止,而是继续将数据读入栈中,这就会覆盖掉其他的数据。比如写入的第17个字符会覆盖掉上图中的var_C.
var_C恰好就是之前的v6。
把鼠标光标放在v6上,就会自动显示v6在栈内的位置。
或者双击下v6。
来自pwnht的评论(闲话/补充?????)
其实这里不用悬停的,或者双击的,因为
滑一下鼠标就可以了,复杂的程序可以悬停,(都行其实2333,怎么快怎么来哈)
(下面是分割线)
上图中的esp是栈顶指针,ebp是栈的基址。
话有些多,直接写脚本吧。
1 | from pwn import * |
第一行,导入pwn模块
第二行,连接到服务器
第四行,sendline发送payload
第五行,进入交互。此时输入cat flag即可拿到flag
payload的前半部分就是16个字符,写满字符串s,第十七个字节就是我们要修改的v6.
p32()可以将99转换成一个32位的数据。
来自pwnht的评论(闲话/补充?????)
这里注意,写expliot脚本的一个好习惯,遇到 read() 要用 send() 不要用 sendline()
这道题目,两个都行,换其他题目可能会多读一个\n (换行符即’\x0a’), (注意大坑,相信我)
还有遇到多次输入的时候,就不能用 send() 函数了,要用 sendafter() 或者 sleep() 来控制流程
附上我的expliot
1 | #!/usr/bin/env python |
基本上差不多哈
0x02 easy_second_try
使用ida64打开
主函数输入姓名,输出姓名,就结束了。
同时可以看到read()明显的栈溢出。
上面的s是输入的字符串,下面的s是ebp(**此处存疑哈 **),下面的r是函数运行完的返回地址。当这个函数运行完时,程序会跳转至r所储存的地址处,继续运行。
参考阅读《加密与解密》P106.
上图的栈和上上图的栈,顺序是相反的,对照的时候注意下。
函数列表内有个sys函数
可以调用shell.
所以我们通过read()函数,多读入一些字节,将r返回地址覆写为sys的地址,就可以调用这个shell了。
1 | from pwn import * |
payload中,0x10是字符串长度,8是8位的ebp,0x400789是sys的地址,p64将其转换为64位的数据
就酱~
0x03 printf
随机一个数字,输入一个数字,二者相等则拿到shell.
第25行存在printf格式化字符串漏洞。
先来看一下相关知识:
我浅显地总结一下:
常见的printf有两(及以上)个参数,如printf("%d", &a);
。但其实printf只需要一个参数,printf("%d%d");
,需要参数时从栈顶依次读入即可。前面的printf("%d", &a);
本质上就是先把a的地址压入栈内,然后printf读栈顶元素并输出。
printf函数的第一个参数就是格式化字符串。
正常的程序,这个格式化字符串应该是写死在代码里的,但是本题printf的那个字符串是我们输入的,所以我们可以通过一些方式来输出一些数据(或写入一些数据)。
1 | %d - 十进制 - 输出十进制整数 |
%x表示输出栈顶的那个十六进制数据,%i$x表示输出偏离栈顶i处的十六进制数据。
我们现来测试下程序运行到printf()处,栈的情况。
did you hear that?输入长度不超过16的字符串即可。然后do you understand输入7个%p,程序输入从栈顶到偏移栈顶7个32位处的7个十六进制数。
第7个参数输出的是%p%p(0x70是p,0x25是%),所以我们确定了输入的%p%p%p%p%p%p%p这个字符串是从栈内偏移7处开始的(ebp+7)。
0x2c处就是我们输入的字符串的起始位置。我们想要知道的那个随机数在0x0C的var_C处。二者偏移量为(0x2c-0x0c)//4=8,除以4是因为这是32位程序。
buf相对栈顶偏移7,var_C相对buf偏移8,即var_C相对栈顶偏移15
不用脚本,直接手撸
拿到随机数0xa08457,转换成十进制数10519639,输进去,拿到shell.
来自pwnht的评论(闲话/补充?????)
你的非预期解呢??????
来自iyzyi的补充
嗯,这道题在pwnht学长提示我之前,我不是这么考虑的。
先说一下格式化字符串中的%n。%n的作用是向保存在栈顶的一个地址处写入一个数,这个数是字符串中位于%n前面的字符的数量。%i$n的作用类似,不过是向偏移栈顶i处保存的地址中写入一个数。比如,对照着上上上图,“abc%6$n”就是向0xf770f244处写入3.
但是,一定要注意,对应的栈中的参数必须是一个合法的地址。比如对照着上上上图,“abc%2$n”就是向0x10处写入3。0x10不是个地址,所以程序会崩溃。
有了以上知识,那我就开始说一下我最初的思路,非预期解。应该可以解题,但是成功的概率实在感人。
我是想通过%n向v8处写入一个数,然后我再输入这个数,自然就可以拿到shell.
但是,万万没有想到,这题v8的地址是一直在改变的,根据我的多次测试,其地址大概位于0xff800000到0xffffffff之间。
于是我就想碰撞一下地址。我在程序里写了个地址,假设它就是字符串的地址,再通过偏移量算出v8的地址。如果某一次程序恰好将字符串加载到我假设的地址处,那么我就可以向v8写入相应的字符数量。
1 | from pwn import * |
如果%7$n不是向一个合法的地址处写入,程序会timeout: the monitored command dumped core\n,报错EOFError。为了程序的程序化运行,捕捉了这个异常。
如果恰好碰撞到了一个地址,但是又不是我们需要的那个目标地址,程序会按照流程输出haha, I know you can't do it! 此时我们使用continue跳过这次碰撞。
对了,说明一下,这个脚本不一定正确哈。我还没有跑出来(跑得CPU都糊了)。有兴趣的可以试试。
为了说明一下成功的概率,我放一个数字:0xffffffff-0xff800000=0x7fffff=2^23=8388608。
BASIC
base大礼包哦
base64->url->base32
最基本的编码表
1 | s = r'\u0063\u0075\u006d\u0074\u0063\u0074\u0066\u007b\u0036\u0036\u0036\u0036\u005f\u0036\u0036\u0036\u005f\u0036\u0036\u005f\u0079\u004f\u0075\u005f\u0041\u0072\u0065\u007d' |
猪关在栅栏里
两次栅栏密码。