i春秋2020新春公益賽WP


Re

Factory

主函數fork了一個子進程,父進程添加了一個信號處理器用於比對input,然后死循環掛起。子進程讀入input,然后調用了關鍵函數。

image-20200224092853714

跟進關鍵函數,發現是從一段內存中讀取數據,然后根據數據發送信號給父進程,感覺像個虛擬機。

代碼很長這里就不貼了,看一眼CFG,符合一般虛擬機的流程圖:

image-20200224103556748

在虛擬機里面沒見到對input的操作,肯定有遺漏的地方,對input查看交叉引用,在init里面發現兩個函數:

image-20200224112325787

第一個函數給input分配了一段內存,第二個函數設置了一堆信號處理器。

這里分配內存用的mmap,並且開了MAP_SHARED,子進程fork的是父進程指向這塊內存的指針,所以這塊內存是父子進程共享的。

要注意的是第一個函數中存在反調試,檢查了父進程的cmdline並且加密了,只有加密后的cmdline是指定數據才分配內存:

image-20200224112601647

很多人動態調試不成功就是因為這個,這題不需要動態調試。如果一定要調,attach或者把這段代碼patch一下都行。

第二個函數中建立了一堆handler,根據這些代碼還原opcode就行。

image-20200224112946254

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的題目,以前沒做過,學了一波。

image-20200224114136359

找到創建窗體之前的位置,跟進sub_402250,在330行附近發現這段代碼:

image-20200224114330903

這個函數把buttonclick、metaobject還有一個函數sub_402150傳了進去,我猜是綁定了回調函數。

跟進sub_402150,在底部找到檢查輸入的代碼:

image-20200224114648681

get_ans邏輯很簡單,可以動態調試直接獲取返回值:

image-20200224115502597

自己算一遍也可以:

#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打開,發現是平坦化:

image-20200224115816161

python deflat EasyEncrypt 0x400A10

反平坦化之后再看邏輯(其實這題不反平坦化也能看個大概,畢竟從提示字符串的內容可以判斷出各個塊的順序):

image-20200224120052653

image-20200224122854164

只加密了前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)

image-20200224123203938

發現是png,覆蓋前16字節即可

flag

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))

image-20200224124642935

11s就出解了

flag{vm_is_not_easy}

Analysis of viruses

還真是病毒分析,出題人真他娘的是個人才。

邏輯很好懂,IDA看看結合一下高中生物知識,代碼都不用讀完就能知道在干啥。

image-20200224125002427

主函數先把輸入的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不知道高到哪去。

QQ截圖20200223161616

flag{AUGUGCCUUGCAAGACUUUUCUCGAUACUUAACGUCUGUGGAAAACUUUAA}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM