首先,我们解压得到一个Misc150.txt文件。这里必须使用Winrar解压,因为其中存在文件流数据。这一点在txt文件以及压缩包大小不一致中也可以看得出来。Misc150.txt中说道 Flag.zip behind me. 暗示着文件流的存在,以及流名称为Flag.zip。我们就使用命令行来访问文件流。
>"C:\Program Files\WinRAR\winrar.exe" Misc150.txt:Flag.zip
打开压缩包,得到一个小压缩包一个图片。其中,小压缩包名字为Steins;Gate
也就是命运石之门的名字。而图片的Christina则是命运石的女主角。在题目的描述中,有说道“命运石之门里没有flag”,这也就暗示着flag不在这两文件里面。这样我们剩下的选择就只有当前这个zip文件了。如果细心的话,会发现压缩包的注释部分有非常长条的空白行,将其三行复制出来,tab用1替换,空格用0替换就可以得到一串二进制。简单判断二进制应为7为ASCII码,解码得flag。
另一种方法是通过Ultra edit等软件,查看包数据也能发现问题。在这一部分有规律的0x09
,0x20
,将其提取出来,替换一下也能得到flag。
拿到流量包,分析可知有两次wpa握手的过程,连接的同一个wifi。第二次握手过程中wireshark根据标志位识别出来的顺序颠倒了,说明握手包有问题。根据wpa认证过程,修改Key Information字段,得到修复的包。然后扔到aircrack去跑,在有hint的情况下三个小时之内能跑出来,得到密码:55672627,然后airdecap解包。本来以为不会有大佬注意到这是被打掉了强制重新连得,没想到大佬们注意到了好细心Orz。
tips:应该先爆破再修复和先修复再爆破是不一定的,aircrack如果不修复就爆破是爆破不出来的,有的工具是通过第一次认证破解的,有的工具通过第二次认证破解,如果不修复的话跑出来与否就看用的啥了,通过第二次认证跑的肯定不修复出不来结果。
总之最后得到通信内容:
re2源码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void readFlag()
{
FILE *pFile=fopen("./flag.txt","r");
char pBuf[32]={0};//socat tcp-l:8000,reuseaddr,fork exec:./re2
fread(pBuf,1,32,pFile);
printf("%s",pBuf);
fclose(pFile);
}
int main()
{
int i,j;
int a[19*19] = {0};
int b[19][19];
int k;
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
for (k = 1; k <=19 ; k=k+2)
{
for (i = 0; i < k; ++i)
{
for (j = 0; j < k; ++j)
{
scanf("%d",&b[i][j]);
a[(b[i][j]-1)%(k*k)] = 1;
}
}
for (i = 0; i < k*k; ++i)
{
if(a[i]==0)
return -1;
}
int num;
srand(time(NULL));
num = rand()%k;
int c1=0,c2=0,c3=0,c4=0;
for (i = 0; i < k; ++i)
{
c1 += b[num][i];
}
for (i = 0; i < k; ++i)
{
c2 += b[i][num];
}
for (i = 0; i < k; ++i)
{
for (j = 0; j < k; ++j)
{
if(i==j) c3 += b[i][j];
if (i+j==k-1) c4+=b[i][j];
}
}
if (c1 != c2 || c3 != c4 || c1 != c3)
return -1;
}
readFlag();
return 0;
}
编写程序并提交结果:
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 20
int main()
{
int matrix[MAXSIZE][MAXSIZE]; /* the magic square */
int count; /* 1..n*n counting */
int row; /* row index */
int column; /* column index */
int order; /* input order */
char line[100];
for (order = 1; order <= 19; order += 2)
{
row = 0; /* start of from the middle */
column = order/2; /* of the first row. */
for (count = 1; count <= order*order; count++) {
matrix[row][column] = count; /* put next # */
if (count % order == 0) /* move down ? */
row++; /* YES, move down one row */
else { /* compute next indices */
row = (row == 0) ? order - 1 : row - 1;
column = (column == order-1) ? 0 : column + 1;
}
}
for (row = 0; row < order; row++) {
for (column = 0; column < order; column++)
printf("%d ", matrix[row][column]);
}
printf("\n");
}
return 0;
}
扫端口和ftp登陆尝试的流量都没啥用。。。
利用proftp的洞:CVE2015-3306,获得了192.168.138.136的shell,写到web目录下一个1.php一句话。然后通过另外一台机器连菜刀,down下来了两个文件:xor.jpg和whoami.jpg。名字提示很明显,xor,没有挖坑,所以两个文件xor一下得到可运行的python代码,逆向这段代码可获得AES_KEY{}。话说为什么会有人提交这个东西呢……明显格式不对啊……
proftpd漏洞利用流量:
然后取得msfshell:
利用msfshell写菜刀一句话:
菜刀连接下载文件,菜刀流量的话,去掉文件头和文件尾的东西就ok了:
图片解密python脚本:
with open("whoami.jpg",'r') as file:
pic=file.read()
temp=''
with open('xor.jpg','r') as file:
t=file.read()
for a,b in zip(t,pic):
print chr(ord(a)^ord(b)),
m="cc90b9054ca67557813694276ab54c67aa93092ec87dd7b539"
def process(a,b,m):
return "".join(map(chr,map(lambda x: (x*a+b)%251,map(ord,m.decode('hex')))))
for i in xrange(255):
for j in xrange(255):
if "AES" in process(i,j,m):
print process(i,j,m)
得到AES_key{},AES啊!不是LCTF!
然后DNS流量异常,找了个DNSshell不能用啊,于是自己xjb写了一个xjb传的shell。发一个baidu.com的开始接收命令,一个命令执行传回去一个google.com。找到这些返回google.com的蜜汁流量,拼到一起用AES_key解密就可以看到:
混进去了一堆xjb访问的http流量,访问了L-team.org的about页面,发现DNS解析了不少dalao博客的地址,就当广告了233333:
最后,解密得到Please submit the used ftp CVE ID like "CVE********"and LCTF{A11_1n_0ne_Pcap} as your flag.eg."CVE12345678A11_1n_0ne_Pcap"
re100设计之初就是消耗耐心的逆向,并没有什么有趣的东西在里面,所以最后的排序函数也只是烂代码增加难度系列(不要打我)。此外Re100最后的排序算法出了一点小问题,需要选手用暴力搜索来解出这道题,不过对题目整体设计没有影响。
算法主体是CBC+字符串排序。外围是Qt的信号槽机制。
排序函数:
for(QVector<int>::iterator iter = pd.begin(); iter != pd.end(); ++iter){
num = *iter;
num = num % key.size();
while(randlist.indexOf(num) != -1){
num = (num + 1) % pd.size();
}
randlist.append(num);
}
void Randlist::randpd(QVector<int> &pd){
int idlist;
int begin_index = 0;
int idpd = begin_index;
do{
idlist = randlist.indexOf(idpd);
std::swap(pd[idlist], pd[begin_index]);
idpd = idlist;
}while(idpd != begin_index);
}
程序流程是,获取输入后先用\x09
补全长度到5的倍数。之后是CBC加密,这里没有使用初始IV。key="Cirno"
,5个字符一组,先和将每一个字符的高4位和低4位交换,再和key异或。除第一组外每组再和前一组异或。最后使用上面的代码和最初(补全后)的输入生成奇怪的位置字典,重排CBC后的字串,进行strcmp
。
re200是一个完(zhi)整(zhang)的文件加密器。当然,解密器我没有写。这题本来是想玩SMC的...但是后来发现写好的SMC对逆向无任何干扰,反而是signal坑了不少人。
程序在main函数执行前(init)先运行了sub_8048901
,用sig == ppid
检测了是否被调试,如果是的话就直接将一个后面用到的表填充为错误数值。之后是取得分页大小,将sub_8048AC3
所在的内存设置为RWX
。最后设置signal(SIGUSR1, sub_80489FB)
。以上这些除了sysconf(),都是int 0x80
实现的。
main函数执行,再一次检测是否有调试(有的话直接exit),获得用户输入,补全为偶数长度,拷贝字符串到全局变量,然后用kill(0, SIGUSR1)
触发了之前设置的signal,跳转到sub_80489FB
。将补全后的字符串分为两个一组,循环调用sub_8048A7B
。在sub_8048A7B
中根据((a>>2)&0x3)
和之前的表得到一个函数地址,调用sub_8048AC3
。
sub_8048AC3
在初始化时被设置为RWX
...利用sub_8048A7B
中的函数地址进行SMC,跳转到取得的函数中。在之后的函数就是对这组字符的移位和异或加密。
signal处理完成,回到正常流程中,再再一次检测是否有调试(出题人:求不打)。对加密完的字串做base16。最后用维吉尼亚算法加密文件,输出。
解题时,维吉尼亚直接在线解密就可以,然后得到key.明文是小说《基督山伯爵》里面随意选的一段话,只是为了帮助进行维吉尼亚解密,并没有实际意义。解密得到key后,程序就和一个crackme没什么区别了...分析算法逆出orignal key即可。测试时是正向暴力破解,反推也没问题。
orignal key = H4ck1ngT0TheGate
此外:
void init_game(void) __attribute__((constructor));
Section header table
,但是ELF文件的执行只需要Program header table
。程序编译完之后直接覆盖了Section header table
,导致gdb直接GG。但是对IDA无效。section header table
也是可以重建的,但是...之后还是有反调试拦着(逃)。测试脚本:
char *strkey = "ieedcpgdhkedddfenkfaifgggcgbbbgf";
char all_a;
char all_b;
int main(int argc, char const *argv[])
{
boom();
return 0;
}
void zero(){
char c;
//c = a
c = (all_a & 0xF0) | (all_b & 0x0F);
all_b = (all_a & 0x0F) | (all_b & 0xF0);
all_a = c;
all_a = all_a ^ 0x99;
all_b = all_b ^ 0x55;
}
void one(){
all_a = all_a ^ all_b;
all_b = ((all_b >> 4) & 0x0F) | ((all_b << 4) & 0xF0);
}
void two(){
//c = a
char c;
c = (all_a & 0x0F) | (((all_b & 0x0F) << 4) & 0xF0);
all_b = ((all_a >> 4) & 0x0F) | (all_b & 0xF0);
all_a = c;
}
void three(){
//and 0xF0 ?
all_a = all_a ^ (all_a << 4);
all_b = all_b ^ (all_b << 4);
}
void boom(){
char end[16];
int map;
int a, b, i;
char tmp_1, tmp_2;
char end_1, end_2, end_3, end_4;
for(i = 0; i < 32; i+=4){
printf("Num %d and %d:\n", i, i+1);
for(a = 0; a <= 0xFF; ++a){
for(b = 0; b <= 0xFF; ++b){
all_a = a;
all_b = b;
map = (a >> 2) & 0x3;
switch(map){
case 0: zero();break;
case 1: one();break;
case 2: two();break;
case 3: three();break;
}
tmp_1 = all_a;
tmp_2 = all_b;
end_1 = (tmp_1 & 0x0F) + 0x61;
end_2 = ((tmp_1 >> 4) & 0x0F) + 0x61;
end_3 = (tmp_2 & 0x0F) + 0x61;
end_4 = ((tmp_2 >> 4) & 0x0F) + 0x61;
if((end_1 == strkey[i]) && (end_2 == strkey[i+1]) && (end_3 == strkey[i+2]) && (end_4 == strkey[i+3])){
printf("%c%c\n", a, b);
}
}
}
}
}
Re300原本的出题人跑路了,我临时帮他出题。随手找了个DDoS木马。本着不互相伤害的原则。没有用MIPS版本,没有去掉符号表。所以就很easy了。
首先,题目提供一个ELF可执行文件(re300)和抓的包(dump.pcap)。提示信息flag(ip:port),那自然是找被打的ip和端口号了。关于此马的详细分析可参考 https://www.sans.org/reading-room/whitepapers/malicious/analyzing-backdoor-bot-mips-platform-35902R_y_RQ 因为使用AES加密数据包,遂家族命名为AES.DDoS。本样本产自China的变种台风ddos,支持多平台MIPS,Windows,Linux等。
首先观察数据包,就那么几条。显然C&C IP为192.168.168.133,上线端口48080,而Victim IP 为192.168.168.148(由第4个数据包可以确定)。那么可以确定第16个数据包,为中控IP像肉鸡发送的指令包。
懂得人自然懂。数据包的格式一般为【指令号+数据内容】在_ConnectServerA
函数里,106行接收buffer,前4个字节表示指令号,当其为6时(118行),执行DealwithDDoS
:
ok,接下来我们可以发现程序使用AES算法开始解密,同时注意到数据包中存在重复的数据7df76b0c1ab899b33e42f047b91b546f
,很容易联想到分组加密的ECB(电码本)模式,并且分组长度是16,这点从key0也可以辅证。
由此我们可以解密出数据包。接下来就是找到数据包中表示ip和port的字段。这个看一下DealwithDDoS(128行)这个函数。数据包的偏移位置0x184表示attackMode(SYN,TCP,UDP...),那0x188处就是轮数了吧。
随便挑一个mode跟进,SYN_Flood函数。40行和41行分别取出port,ip。至此,题目想要的数据已经分析出来了。
最后就是写脚本啦:
from Crypto.Cipher import AES
from struct import unpack
from hashlib import md5
with open('./re300') as fp:
fp.seek(0xe81ff)
key = fp.read(16)
with open('./dump.pcap') as fp:
data = fp.read()
idex = data.find('\x06\x00\x00\x00')
data = data[idex+4:idex+0x1a4]
aes = AES.new(key , AES.MODE_ECB)
text = aes.decrypt(data)
ip = text[:text.find('\x00')]
port = unpack('<H',text[0x1d4-0x54:][:2])[0]
flag = md5(ip+':'+str(port)).hexdigest()
print 'flag is: {0}'.format(flag)
题目试图用magic_file作为块链数据挖矿,挖到前3400个block之后,用他们的哈希值生成IV和Key,解密Flag字符串。题目除了去除了符号表之外,没有任何反调试和反分析内容,基本就是考察算法逆向能力。题目的一个切入点就是,通过trace可以发现在一系列函数中耗了很长时间,由此可以发现SHA256的相关代码,再分析上下文的代码,也就差不多能看出挖矿的过程了。结合题目前期给出的UID(转换成十进制搜索,可以发现是第一个block的Nonce值),以及后期给出的Bitcoin提示,应该是能明白程序在干什么的。
相关代码已经上传到Github了,这里就不多介绍。
关于Re500,首先向大家道歉,由于时间关系,出题人手残把SIU_PGPDO
和SIU_PGPDI
的地址值写反了,比赛结束后有队伍报告了这个问题……
这道题是一个跑在嵌入式设备中的程序,目标芯片是Freescale的MPC5674F,使用了Erika RTOS。程序原理是,初始化外部的LCD1602,并将flag打印到屏幕上。在设计的时候,我埋下的几个坑点有:
同样把代码传到了Github上,大家可以自行查阅。
拿到题后先正确的载入到IDA中,然后最简单的方法是直接查看字符串,有一段以FAIL开头的字符串,找到引用函数,然后直接去读。这种方法根本不需要对RTOS和业务代码做区分,比较快。通过向外发送的控制字,以及将数组取出分析,可以知道这应该是在送字模,但是字模是乱掉的。解决办法就是,要么自己根据FLAG格式推测,要么从送控制字之前的几个移位操作分析出硬件连线。
解题脚本如下:
#!/usr/bin/python
srctext="FAIL{tX1NJfGxnatFxY63Wxmlpxh12Z0h5xeGxaH56Nfy}" #get from bin
charmap={ # get charmap from bin
0xA9:[0x46, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x46],
0xAA:[0x00, 0x00, 0x00, 0x15, 0x15, 0x46, 0x42, 0x42],
0xAB:[0x00, 0x00, 0x00, 0x44, 0x03, 0x45, 0x03, 0x45],
0xAC:[0x00, 0x00, 0x00, 0x47, 0x02, 0x44, 0x01, 0x47],
0xAD:[0x00, 0x00, 0x00, 0x45, 0x03, 0x02, 0x02, 0x45],
0xAE:[0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x46],
0xAF:[0x53, 0x53, 0x53, 0x53, 0x15, 0x15, 0x15, 0x15],
0xA0:[0x12, 0x02, 0x02, 0x46, 0x03, 0x03, 0x03, 0x46],
0xA1:[0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x57],
0xA8:[0x46, 0x11, 0x11, 0x40, 0x04, 0x02, 0x10, 0x57],
0xF9:[0x00, 0x00, 0x00, 0x44, 0x03, 0x47, 0x02, 0x45],
0xFA:[0x00, 0x00, 0x00, 0x56, 0x15, 0x15, 0x15, 0x15],
0xFB:[0x46, 0x11, 0x11, 0x46, 0x11, 0x11, 0x11, 0x46],
0xFC:[0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x44],
0xFD:[0x04, 0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x46],
0xFE:[0x46, 0x11, 0x01, 0x44, 0x01, 0x01, 0x11, 0x46],
0xFF:[0x00, 0x00, 0x00, 0x53, 0x06, 0x02, 0x02, 0x16],
0xF0:[0x56, 0x03, 0x03, 0x46, 0x02, 0x02, 0x02, 0x16],
0xF1:[0x47, 0x11, 0x10, 0x06, 0x40, 0x01, 0x11, 0x56],
0xF2:[0x46, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x46],
0xF3:[0x47, 0x40, 0x40, 0x40, 0x40, 0x40, 0x50, 0x16],
0xF4:[0x00, 0x00, 0x00, 0x53, 0x03, 0x03, 0x03, 0x45],
0xF5:[0x56, 0x03, 0x03, 0x46, 0x03, 0x03, 0x03, 0x56],
0xF6:[0x56, 0x03, 0x03, 0x46, 0x42, 0x03, 0x03, 0x17],
0xF7:[0x17, 0x03, 0x42, 0x06, 0x42, 0x42, 0x03, 0x17],
0xE8:[0x57, 0x50, 0x40, 0x04, 0x04, 0x04, 0x04, 0x04],
0xE9:[0x40, 0x00, 0x44, 0x40, 0x40, 0x40, 0x40, 0x16],
0xEA:[0x53, 0x03, 0x07, 0x07, 0x43, 0x43, 0x03, 0x17],
0xEB:[0x40, 0x44, 0x42, 0x42, 0x50, 0x47, 0x40, 0x41],
0xEC:[0x04, 0x04, 0x44, 0x42, 0x42, 0x47, 0x03, 0x13],
0xED:[0x00, 0x56, 0x03, 0x03, 0x03, 0x46, 0x02, 0x16],
0xEE:[0x00, 0x00, 0x00, 0x47, 0x40, 0x04, 0x04, 0x47],
0xEF:[0x45, 0x03, 0x10, 0x10, 0x51, 0x11, 0x03, 0x44],
0xE0:[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57],
0xE1:[0x56, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x56],
0xE2:[0x57, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x57],
0xD9:[0x47, 0x11, 0x10, 0x10, 0x10, 0x10, 0x11, 0x46],
0xDA:[0x12, 0x02, 0x02, 0x43, 0x42, 0x46, 0x03, 0x17],
0xDB:[0x00, 0x00, 0x00, 0x44, 0x03, 0x03, 0x03, 0x44],
0xDC:[0x13, 0x03, 0x03, 0x47, 0x03, 0x03, 0x03, 0x13],
0xDD:[0x13, 0x03, 0x03, 0x42, 0x42, 0x44, 0x04, 0x04],
0xDE:[0x16, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x57],
0xDF:[0x57, 0x10, 0x10, 0x56, 0x01, 0x01, 0x11, 0x46],
0xD0:[0x00, 0x00, 0x00, 0x53, 0x42, 0x04, 0x42, 0x53],
0xD1:[0x57, 0x15, 0x04, 0x04, 0x04, 0x04, 0x04, 0x46],
0xD2:[0x12, 0x02, 0x02, 0x46, 0x03, 0x03, 0x03, 0x17],
0xD3:[0x53, 0x42, 0x42, 0x04, 0x04, 0x04, 0x04, 0x46],
0xD4:[0x57, 0x03, 0x42, 0x46, 0x42, 0x02, 0x02, 0x16],
0xD5:[0x46, 0x11, 0x11, 0x11, 0x47, 0x01, 0x03, 0x46],
0xD6:[0x00, 0x04, 0x04, 0x46, 0x04, 0x04, 0x04, 0x41],
0xD7:[0x46, 0x50, 0x10, 0x56, 0x11, 0x11, 0x11, 0x46],
0xC8:[0x00, 0x45, 0x03, 0x03, 0x03, 0x45, 0x01, 0x41],
0xC9:[0x00, 0x17, 0x03, 0x42, 0x44, 0x04, 0x04, 0x12],
0xCA:[0x46, 0x11, 0x11, 0x11, 0x17, 0x51, 0x46, 0x41],
0xCB:[0x00, 0x00, 0x00, 0x17, 0x03, 0x42, 0x44, 0x04],
0xCC:[0x53, 0x42, 0x42, 0x04, 0x04, 0x42, 0x42, 0x53],
0xCD:[0x57, 0x50, 0x40, 0x04, 0x04, 0x02, 0x03, 0x57],
0xCE:[0x15, 0x15, 0x15, 0x46, 0x42, 0x42, 0x42, 0x42],
0xCF:[0x00, 0x45, 0x03, 0x44, 0x02, 0x47, 0x02, 0x45],
0xC0:[0x00, 0x00, 0x00, 0x56, 0x03, 0x03, 0x03, 0x17],
0xC1:[0x41, 0x04, 0x04, 0x47, 0x04, 0x04, 0x04, 0x47],
0xC2:[0x57, 0x03, 0x42, 0x46, 0x42, 0x02, 0x03, 0x57],
0xC7:[0x41, 0x01, 0x01, 0x45, 0x03, 0x03, 0x03, 0x45],
0xE3:[0x41, 0x40, 0x40, 0x04, 0x40, 0x40, 0x40, 0x41],
0xE5:[0x06, 0x04, 0x04, 0x40, 0x04, 0x04, 0x04, 0x06]
}
for i in srctext:
c = list(charmap[0x98^ord(i)])
c = [0]*(8-len(c))+c
for r in c:
if type(r)==str:
r = ord(r)
# swap the io line
r=((r&0b00000001)) |((r&0b00000010)<<2) |((r&0b00000100)) |((r&0b00001000)>>3) |((r&0b00010000)) |((r&0b00100000)) |((r&0b01000000)>>5) |((r&0b10000000))
# convert to graph
r = bin(r)[2:]
r = '0'*(8-len(r))+r
r = r.replace('0',' ')
r = r.replace('1','\x18')
print(r)
input()