i春秋2020新春公益赛WP
Re
Factory
主函数fork了一个子进程,父进程添加了一个信号处理器用于比对input,然后死循环挂起。子进程读入input,然后调用了关键函数。

跟进关键函数,发现是从一段内存中读取数据,然后根据数据发送信号给父进程,感觉像个虚拟机。
代码很长这里就不贴了,看一眼CFG,符合一般虚拟机的流程图:

在虚拟机里面没见到对input的操作,肯定有遗漏的地方,对input查看交叉引用,在init里面发现两个函数:

第一个函数给input分配了一段内存,第二个函数设置了一堆信号处理器。
这里分配内存用的mmap,并且开了MAP_SHARED,子进程fork的是父进程指向这块内存的指针,所以这块内存是父子进程共享的。
要注意的是第一个函数中存在反调试,检查了父进程的cmdline并且加密了,只有加密后的cmdline是指定数据才分配内存:

很多人动态调试不成功就是因为这个,这题不需要动态调试。如果一定要调,attach或者把这段代码patch一下都行。
第二个函数中建立了一堆handler,根据这些代码还原opcode就行。

opcdode:
17,52,0,42,5,16,20,9,23,0,32,5,3,17,29,6,0,0,5,3,17,64,6,0,64,5,17,29,23,14,1,21,4,15,1,22,2,0,0,4,3,5,16,20,50,5,9,2,19,29,5,18,21,4,16,20,61,10,1,19,52,3,4,18,14,1,21,4,7,1,22,2,0,0,4,3,5,16,20,85,5,9,1,19,64,5,18
还原出来的伪代码(括号内是opcode,冒号前是signal的值,注释是语句含义):
0:
(17,52)42:q1[19]++;input[256+q1[19]-1]=q1[20];q1[20]=input[79]=52;//call 52
2:
(0,42)34:q1[19]++;input[256+q1[19]-1]=42;
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=42=strlen
(16)41:q1[21]=q1[16]==q1[17];
(20,9)45:if(q1[21])q1[20]=input[79]=9;//长度不是42就退出
(0,32)34:q1[19]++;input[256+q1[19]-1]=32;
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=32
(3)34:q1[19]++;input[256+q1[19]-1]=q1[18];//push len
(17,29)42:q1[19]++;input[256+q1[19]-1]=q1[20];q1[20]=input[79]=29;//call 29
(6)35:q1[19]--;q1[18]=input[256+q1[19]];//q1[18]=len
(0,0)34:q1[19]++;input[256+q1[19]-1]=0;
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=0
(3)34:q1[19]++;input[256+q1[19]-1]=q1[18];/push len
(17,64)42:q1[19]++;input[256+q1[19]-1]=q1[20];q1[20]=input[79]=64;//call 64
(6)35:q1[19]--;q1[18]=input[256+q1[19]];//q1[18]=len
(0,64)34:q1[19]++;input[256+q1[19]-1]=64;
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=64
(17,29)42:q1[19]++;input[256+q1[19]-1]=q1[20];q1[20]=input[79]=29;//call 29
(23):break
29:
(14,1)39:q1[18]-=input[79]=1;// q1[18]=41
(21)46:q1[19]++;input[256+q1[19]-1]=input[q1[18]]
(4)35:q1[19]--;q1[16]=input[256+q1[19]];//q1[16]=input[q1[18]]
(15)40:q1[16]^=q1[17];
(1)34:q1[19]++;input[256+q1[19]-1]=q1[16];
(22)47:q1[19]--;input[q1[18]]=input[256+q1[19]];//str^=32,34,36...
(2)34:q1[19]++;input[256+q1[19]-1]=q1[17];//push 32,34,36...
(0,0)34:q1[19]++;input[256+q1[19]-1]=0;
(4)35:q1[19]--;q1[16]=input[256+q1[19]];
(3)34:q1[19]++;input[256+q1[19]-1]=q1[18];
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=q1[18]
(16)41:q1[21]=q1[16]==q1[17];//如果q[17]=0就返回
(20,50)45:if(q1[21])q1[20]=input[79]=50;//je
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//q1[17]=32,34,36...
(9,2)37:q1[17]+=input[79]=2
(19,29)44:q1[20]=input[79]=29;//jmp
50:
(5)35:q1[19]--;q1[17]=input[256+q1[19]];//pop q1[17]
(18)43:q1[19]--;q1[20]=input[256+q1[19]];//retn to 2
52:
(21)46:q1[19]++;input[256+q1[19]-1]=input[q1[18]]
(4)35:q1[19]--;q1[16]=input[256+q1[19]];//q1[16]=input[q1[18]]
(16)41:q1[21]=q1[16]==q1[17];
(20,61)45:if(q1[21])q1[20]=input[79]=61;//je 当循环到字符串尾跳转
(10,1)37:q1[18]+=input[79]=1;
(19,52)44:q1[20]=input[79]=52;//jmp
61:
(3)34:q1[19]++;input[256+q1[19]-1]=q1[18];
(4)35:q1[19]--;q1[16]=input[256+q1[19]];//q1[16]=q1[18]=42
(18)43:q1[19]--;q1[20]=input[256+q1[19]];//retn goto 0
64:
(14,1)39:q1[18]-=input[79]=1;
(21)46:q1[19]++;input[256+q1[19]-1]=input[q1[18]]
(4)35:q1[19]--;q1[16]=input[256+q1[19]];
(7)36:q1[16]+=q1[17]//+=0,1,2
(1)34:q1[19]++;input[256+q1[19]-1]=q1[16];
(22)47:q1[19]--;input[q1[18]]=input[256+q1[19]];//+q1[17]=0,1,2,3,4,5...
(2)34:q1[19]++;input[256+q1[19]-1]=q1[17];
(0,0)34:q1[19]++;input[256+q1[19]-1]=0;
(4)35:q1[19]--;q1[16]=input[256+q1[19]];
(3)34:q1[19]++;input[256+q1[19]-1]=q1[18];
(5)35:q1[19]--;q1[17]=input[256+q1[19]];
(16)41:q1[21]=q1[16]==q1[17];
(20,85)45:if(q1[21])q1[20]=input[79]=85;//je
(5)35:q1[19]--;q1[17]=input[256+q1[19]];
(9,1)37:q1[17]+=input[79]=1//q1[17]++
(19,64)44:q1[20]=input[79]=64;//jmp
85:
(5)35:q1[19]--;q1[17]=input[256+q1[19]];// pop q1[17]
(18)43:q1[19]--;q1[20]=input[256+q1[19]];//retn to 2
比较容易发现,q1[16]、q1[17]、q1[18]是通用寄存器,q1[19]是栈指针,q1[20]是EIP,input[256]是栈底,算法不是很难,异或之后加一些值再异或,脚本:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
#define foru(i,a,b) for(int i=a;i<=b;i++)
#define ford(i,a,b) for(int i=a;i>=b;i--)
int main(){
	unsigned char flag[]="\xaf\xd4\xb8\xbd\xbc\xb9\xfc\xf1\xf6\xa1\xf5\xfe\xf1\xe9\x0b\xf3\x22\x0f\x14\xe2\xed\xe5\xe2\x1f\x56\x54\x4b\x3a\x7e\x3e\x5a\x5a\x5d\x0b\x6b\x68\x54\x54\x64\x07\x51\x1d";
	ford(i,41,0)flag[i]^=(64+(41-i)*2);
	ford(i,41,0){flag[i]-=41-i;flag[i]=(flag[i]+128)%128;}
	ford(i,41,0)flag[i]^=(32+(41-i)*2);
	foru(i,0,41)printf("%c",(flag[i]+128)%128);
}
flag{e171a284-49e7-4817-ad8d-b704c02309e0}
吃鸡神器
是一道QT的题目,以前没做过,学了一波。

找到创建窗体之前的位置,跟进sub_402250,在330行附近发现这段代码:

这个函数把buttonclick、metaobject还有一个函数sub_402150传了进去,我猜是绑定了回调函数。
跟进sub_402150,在底部找到检查输入的代码:

get_ans逻辑很简单,可以动态调试直接获取返回值:

自己算一遍也可以:
#include<cstdio>
using namespace std;
typedef long long LL;
#define foru(i,a,b) for(int i=a;i<=b;i++)
#define ford(i,a,b) for(int i=a;i>=b;i--)
char key[]="lubenwei";
int main(){
	int x=5381;//402101
	foru(i,0,7){
		x+=32*x+key[i];
	}
	printf("%x",x);
}
flag{41d26f00}
EasyEncrypt
IDA打开,发现是平坦化:

python deflat EasyEncrypt 0x400A10
反平坦化之后再看逻辑(其实这题不反平坦化也能看个大概,毕竟从提示字符串的内容可以判断出各个块的顺序):


只加密了前16字节,key是thisisthekey!!!!
解密:
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
def decrypt(text):
    key = 'thisisthekey!!!!'.encode('utf-8')
    mode = AES.MODE_ECB
    cryptor = AES.new(key, mode)
    plain_text = cryptor.decrypt(a2b_hex(text))
    return (plain_text)
if __name__ == '__main__':
    s="398A08C585DB8F7EC8F2BF7986B68782"
    d = decrypt(s)
    print(d)

发现是png,覆盖前16字节即可

EasyVM
打开之后发现真的是EasyVM,opcode都写明白啥意思了,比day1的VM简单很多。由于过于简单,这里选择angr一把梭:
import angr
import claripy
import time
def main():
    p = angr.Project('EasyVM',auto_load_libs=False)
    st = p.factory.entry_state()
    base=0x400000
    sm = p.factory.simulation_manager(st)
    sm.explore(find=base+0xB73,avoid=base+0xB81)
    print(sm.found[0].posix.dumps(0))
if __name__ == "__main__":
    before = time.time()
    print(main())
    after = time.time()
    print("Time elapsed: {}".format(after - before))

11s就出解了
flag{vm_is_not_easy}
Analysis of viruses
还真是病毒分析,出题人真他娘的是个人才。
逻辑很好懂,IDA看看结合一下高中生物知识,代码都不用读完就能知道在干啥。

主函数先把输入的RNA逆转录成DNA,然后再转录成RNA丢进链表里,接着把密码子转化出反密码子,再转化为氨基酸,最后和一段氨基酸序列对比。
由于氨基酸对应密码子不唯一,写个程序爆破:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<algorithm>
#include<map>
#include<vector>
#include "md5.h"
#include<iostream>
#include<cmath>
using namespace std;
typedef long long LL;
#define foru(i,a,b) for(int i=a;i<=b;i++)
#define ford(i,a,b) for(int i=a;i>=b;i--)
const int n=16;
const int m=64;
string a[m]={"Phe","Phe","Leu","Leu","Ser","Ser","Ser","Ser","Tyr","Tyr","sto","sto","Cys","Cys","sto","Trp","Leu","Leu","Leu","Leu","Pro","Pro","Pro","Pro","His","His","Gln","Gln","Arg","Arg","Arg","Arg","Ile","Ile","Ile","Met","Thr","Thr","Thr","Thr","Asn","Asn","Lys","Lys","Ser","Ser","Arg","Arg","Val","Val","Val","Val","Ala","Ala","Ala","Ala","Asp","Asp","Glu","Glu","Gly","Gly","Gly","Gly"};
string b[n]={"Met","Cys","Leu","Ala","Arg","Leu","Phe","Ser","Ile","Leu","Asn","Val","Cys","Gly","Lys","Leu"};
string ans;
int count=0;
MD5 md5;
map<string,vector<string> > mp;
void print(int k){
	int tmp[4],cnt=0;
	while(k){
		tmp[++cnt]=k%4;
		k/=4;
	}
	ford(i,3,1)
		if(tmp[i]==0)printf("A");
		else if(tmp[i]==1)printf("G");
		else if(tmp[i]==2)printf("U");
		else if(tmp[i]==3)printf("C");
}
void dfs(int k){
	if(k==n){
	    md5.reset();
	    md5.update(ans+"UAA");
	    if(md5.toString().substr(0,8)=="e03657e0"){
	    	cout<<ans+"UAA"<<endl;
		}
	    md5.reset();
	    md5.update(ans+"UAG");
	    if(md5.toString().substr(0,8)=="e03657e0"){
	    	cout<<ans+"UAG"<<endl;
		}
	    md5.reset();
	    md5.update(ans+"UGA");
	    if(md5.toString().substr(0,8)=="e03657e0"){
	    	cout<<ans+"UGA"<<endl;
		}
		return;
	}
	std::vector<int>::size_type x= mp[b[k]].size();
	for(std::vector<int>::size_type i = 0; i !=x; i++){
		string tmps=ans;
		ans+=mp[b[k]][i];
		dfs(k+1);
		ans=tmps;
	}
}
int main(){
	foru(i,0,n-1){
		if(!mp[b[i]].empty())continue;
		foru(j,0,m-1){
			if(b[i]==a[j]){
				string tmps="";int k=j;
				int tmp[4],cnt=0;
				foru(l,1,3)tmp[l]=0;
				while(k){
					tmp[++cnt]=k%4;
					k/=4;
				}
				ford(l,3,1)
					if(tmp[l]==0)tmps+="U";
					else if(tmp[l]==1)tmps+="C";
					else if(tmp[l]==2)tmps+="A";
					else if(tmp[l]==3)tmps+="G";
				mp[b[i]].push_back(tmps);
			}
		}
	}
	dfs(0);
}
如果是6位md5有几十组解,后来联系技术支持拿8位md5跑了一下,出了两组解,第二组就是flag。
程序开O2跑了675s,c++速度比python不知道高到哪去。

flag{AUGUGCCUUGCAAGACUUUUCUCGAUACUUAACGUCUGUGGAAAACUUUAA}
i春秋2020新春公益赛WP的更多相关文章
- 2020新春公益赛 writeup
		简单的招聘系统 无需注册账号,admin'or 1#登陆,到blank page页面,在输入key处发现有注入点: /pages-blank.php?key=1%27+union+select+1%2 ... 
- 2020 网鼎杯wp
		2020 网鼎杯WP 又是划水的一天,就只做出来4题,欸,还是太菜,这里就记录一下做出的几题的解题记录 AreUSerialz 知识点:反序列化 打开链接直接给出源码 <?php include ... 
- 2020 i春秋新春战疫公益赛 misc
		0x01 code_in_morse morse decode后得到: RFIE4RYNBINAUAAAAAGUSSCEKIAAAAEUAAAAA7AIAYAAAAEPFOMTWAAABANUSRCB ... 
- i春秋公益赛 ezpload
		题目思路:一看解出的人比较多,emmm,传个马,命令执行一下.最后读到flag文件. /readflag,可执行. 题对萌新比较友好...... 考点:Linux命令,文件上传,命令执行. http: ... 
- i春秋公益赛之signin
		题目链接:https://buuoj.cn/challenges#gyctf_2020_signin 查看程序保护 只开了canary和NX保护,在IDA查看反编译出来的为代码时发现程序给了一个后门 ... 
- i春秋公益赛之BFnote
		题目链接:https://buuoj.cn/challenges#gyctf_2020_bfnote 首先检查程序开的保护: 发现程序只开了canary和NX保护,接下来查看IDA反汇编出来的为代码, ... 
- 2020 10月CUMTCTF wp
		华为杯 × 签到杯√ 论比赛过程来说没什么很大收获 但看师傅们的wp感触很多 赛后复现慢慢学吧 Web babyflask flask ssti模板注入: payload{{key}}发现[]以及类似 ... 
- 2020 天翼杯 部分wp
		天翼杯 呜呜呜呜 是我太菜了 Web APItest 源码 const express = require("express"); const cors = require(&qu ... 
- web赛题2
		@上海赛wp 微信 和 https://www.ctfwp.com/articals/2019unctf.html 后续公告https://unctf.buuoj.cn/notice.html 必看! ... 
随机推荐
- Redis 详解 (八) 主从复制
			目录 1.修改配置文件 2.设置主从关系 3.测试主从关系 4.哨兵模式 5.主从复制原理 6.主从复制的缺点 前面介绍Redis,我们都在一台服务器上进行操作的,也就是说读和写以及备份操作都是在一台 ... 
- 百度easydl之图像分类构建是否佩戴口罩模型
			2020-02-14 今天试了下百度esaydl的图像分类方面的功能,其优点是主需要上传自己的数据集,不需要关注模型训练,就可以得到相应的结果.最后得到的模型可以调用云api在本地进行运行. 网址:h ... 
- 七十五、SAP中数据库的使用SQL
			一.在SAP中可以使用两张数据库,一直是NativeSQL和OPEN SQL. Native SQL(本地SQL)特点: 1.每种关系型数据库都有其对应的 SQL,是数据库相关的. 2.不同的 SA ... 
- zerone 01串博弈问题
			近日领到了老师的期末作业 其中有这道 01 串博弈问题: 刚开始读题我也是云里雾里 但是精读数遍 “细品” 之后,我发现这是一个 “动态规划” 问题.好嘞,硬着头皮上吧. 分析问题:可知对每个人有两手 ... 
- 吴裕雄--天生自然C++语言学习笔记:C++ 接口(抽象类)
			接口描述了类的行为和功能,而不需要完成类的特定实现. C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念. 如果类中至少有一个函数被声明为纯虚 ... 
- Bulma CSS - CSS类
			Bulma CSS框架教程 Bulma CSS – 简介 Bulma CSS – 开始 Bulma CSS – CSS类 Bulma CSS – 模块化 Bulma CSS – 响应式 Bulma是一 ... 
- 微信小程序拒绝授权后提示信息以及重新授权
			wx.authorize({ scope: 'scope.writePhotosAlbum', success() { // 授权成功 wx.saveImageToPhotosAlbum({ file ... 
- java项目提交到码云
			1.在码云上面创建一个新的项目,用于存放提交的项目内容 2.在需要共享的项目上右键-->team-->Share Project分享项目-->勾选 Use or create rep ... 
- CSS3 之高级动画(6)CSS3 clip-path属性实现的几何图形变形动画
			clip-path 属性介绍: clip-path属性可以创建一个只有元素的部分区域可以显示的剪切区域. 区域内的部分显示,区域外的隐藏. 剪切区域是被引用内嵌的URL定义的路径或者外部svg的路径. ... 
- 用ps画一个Gif的小房子(1)
			效果如图: 制作方法: 1.新建200*200的画布:复制一块小房子图片 2.点击窗口-时间轴-勾选帧动画 3.如图所示(我这边是一帧对应一个图层) 4.新建图层-这边要新建24个图层,每个图层对应不 ... 
