本帖最后由 一筐萝卜 于 2018-11-19 19:10 编辑
CPP
- 题目是一个64位ELF文件
- 拖入IDA中f5查看main函数伪代码
[Asm] 纯文本查看 复制代码
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char v4; // [rsp+0h] [rbp-80h]
char v5; // [rsp+20h] [rbp-60h]
char v6; // [rsp+40h] [rbp-40h]
unsigned __int64 v7; // [rsp+68h] [rbp-18h]
v7 = __readfsqword(0x28u);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v4, a2, a3);
std::operator<<<std::char_traits<char>>(&std::cout, "input flag:");
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v4);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v6, &v4);
sub_4010A2((__int64)&v5, (__int64)&v6, (__int64)&v6);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v6);
sub_401332((__int64)&v5);
sub_40154E((__int64)&v5);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v4);
return 0LL;
}
- 通过伪代码可以看出来,这是一个用C++写的
- 这么长一串其实是引用了C++的字符串类,当时我也是没有看懂,也是第一次遇到
[Asm] 纯文本查看 复制代码
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string
- 通过引用字符串"input flag:"我们可以推断出“std::operator<<<std::char_traits<char>>(&std::cout, "input flag:");”是输出字符串的意思,而std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v4)是输入字符串的意思
- 通过动态调试可以发现std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v6, &v4);这个函数是将v4的值复制到v6
- 然后进入sub_4010A2,通过看汇编代码可以发现,这个函数其实是传入两个参数“sub_4010A2(&v5,&v6)”
[Asm] 纯文本查看 复制代码
void __fastcall sub_4010A2(__int64 a1, __int64 a2, __int64 a3){
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(a1, a2, a3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1, a2);// 把a2的值复制到a1
sub_40111A(a1);
sub_40110E();
}
- 在函数里面首先是将a2的值传入a1中,也就是将v6的值传入v5
- 然后进入sub_40111A()
[Asm] 纯文本查看 复制代码
unsigned __int64 __fastcall sub_40111A(__int64 a1)
{
_BYTE *v1; // rbx
int v2; // er12
const char *v3; // rax
int i; // [rsp+1Ch] [rbp-54h]
char s1; // [rsp+20h] [rbp-50h]
char v7; // [rsp+21h] [rbp-4Fh]
char v8; // [rsp+22h] [rbp-4Eh]
char v9; // [rsp+23h] [rbp-4Dh]
char v10; // [rsp+24h] [rbp-4Ch]
char v11; // [rsp+25h] [rbp-4Bh]
char v12; // [rsp+26h] [rbp-4Ah]
char v13; // [rsp+27h] [rbp-49h]
char v14; // [rsp+28h] [rbp-48h]
char v15; // [rsp+29h] [rbp-47h]
char v16; // [rsp+2Ah] [rbp-46h]
char v17; // [rsp+2Bh] [rbp-45h]
char v18; // [rsp+2Ch] [rbp-44h]
char v19; // [rsp+2Dh] [rbp-43h]
char v20; // [rsp+2Eh] [rbp-42h]
char v21; // [rsp+2Fh] [rbp-41h]
char v22; // [rsp+30h] [rbp-40h]
char v23; // [rsp+31h] [rbp-3Fh]
char v24; // [rsp+32h] [rbp-3Eh]
char v25; // [rsp+33h] [rbp-3Dh]
char v26; // [rsp+34h] [rbp-3Ch]
char v27; // [rsp+35h] [rbp-3Bh]
char v28; // [rsp+36h] [rbp-3Ah]
char v29; // [rsp+37h] [rbp-39h]
char v30; // [rsp+38h] [rbp-38h]
char v31; // [rsp+39h] [rbp-37h]
char v32; // [rsp+3Ah] [rbp-36h]
char v33; // [rsp+3Bh] [rbp-35h]
char v34; // [rsp+3Ch] [rbp-34h]
char v35; // [rsp+3Dh] [rbp-33h]
char v36; // [rsp+3Eh] [rbp-32h]
char v37; // [rsp+3Fh] [rbp-31h]
char v38; // [rsp+40h] [rbp-30h]
char v39; // [rsp+41h] [rbp-2Fh]
char v40; // [rsp+42h] [rbp-2Eh]
char v41; // [rsp+43h] [rbp-2Dh]
char v42; // [rsp+44h] [rbp-2Ch]
char v43; // [rsp+45h] [rbp-2Bh]
char v44; // [rsp+46h] [rbp-2Ah]
char v45; // [rsp+47h] [rbp-29h]
char v46; // [rsp+48h] [rbp-28h]
char v47; // [rsp+49h] [rbp-27h]
char v48; // [rsp+4Ah] [rbp-26h]
unsigned __int64 v49; // [rsp+58h] [rbp-18h]
v49 = __readfsqword(0x28u);
s1 = 0x99u;
v7 = 0xB0u;
v8 = 0x87u;
v9 = 0x9Eu;
v10 = 0x84u;
v11 = 0xA0u;
v12 = 0xCBu;
v13 = 0xEFu;
v14 = 0x88u;
v15 = 0x90u;
v16 = 0xBBu;
v17 = 0x8Eu;
v18 = 0x91u;
v19 = 0xE0u;
v20 = 0xD2u;
v21 = 0xAEu;
v22 = 0xD4u;
v23 = 0xC5u;
v24 = 0x6F;
v25 = 0xD7u;
v26 = 0xC0u;
v27 = 0x68;
v28 = 0xC6u;
v29 = 0x6A;
v30 = 0x81u;
v31 = 0xC9u;
v32 = 0xB7u;
v33 = 0xD7u;
v34 = 0x61;
v35 = 4;
v36 = 0xDAu;
v37 = 0xCFu;
v38 = 0x3D;
v39 = 0x5C;
v40 = 0xD6u;
v41 = 0xEFu;
v42 = 0xD0u;
v43 = 0x58;
v44 = 0xEFu;
v45 = 0xF2u;
v46 = 0xADu;
v47 = 0xADu;
v48 = 0xDFu;
for ( i = 0;
i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(a1);
++i )
{
v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, i);// a1[i]
v2 = 4 * *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, i);
*v1 = ((*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, i) >> 6) | v2) ^ i;
}
v3 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(a1);
if ( strcmp(&s1, v3) == 0 )
{
sub_4012CE(a1);
exit(0);
}
return __readfsqword(0x28u) ^ v49;
}
- 然后经过调试发现std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]的意思是取我们输入的第i个值,即a1
[Asm] 纯文本查看 复制代码
.text:0000000000401234 movzx eax, byte ptr [rax]
.text:0000000000401237 movsx eax, al
.text:000000000040123A shl eax, 2
- 首先是先将a1[\i]进行左移两位
- 然后将a1的值算术右移6,然后与v2进行或运算,即与“a<<2”进行或运算,随后与i的值进行异或
- 这一轮下来,总结一下这个循环干了什么:a=(a<<2|a>>6)^i,该循环将a1的每一位都进行这样的操作
- 当循环结束后,与s1进行比较,所以我们要把s1的数据提取出来,然后根据刚刚的运算进行反推,脚本如下:
[Python] 纯文本查看 复制代码
s1 = [0x99,0xB0,0x87,0x9E, 0x84, 0xA0, 0xCB, 0xEF, 0x88, 0x90, 0xBB, 0x8E, 0x91, 0xE0, 0xD2, 0xAE, 0xD4, 0xC5, 0x6F, 0xD7, 0xC0, 0x68, 0xC6, 0x6A, 0x81, 0xC9, 0xB7, 0xD7, 0x61, 4, 0xDA, 0xCF, 0x3D, 0x5C, 0xD6, 0xEF, 0xD0, 0x58, 0xEF, 0xF2, 0xAD, 0xAD, 0xDF]
flag = ""
for x in range(0,len(s1)):
for asc in range(32,128):
if (asc>>6|asc<<2)&0xff==s1[x]^x:
flag+= chr(asc)
break
print flag
- 我在写脚本的时候也遇到一个错误,就是我之前没遇到过,最开始我的脚本是这样的,“(asc>>6|asc<<2)==s1[x]^x”,然后死活也跑不出来东西,经过查阅之后发现还需要加一个&0xff的操作
- 该脚本跑出来的是“flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}”
- 当比较成功之后会进入sub_4012CE()函数里面,这个函数的作用就是输出两句话:“good job,but .....”,很显然,题目并没有这个简单,接着往下面分析
- std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string,该函数应该是把刚刚分配的变量v6给撤销掉,然后进入sub_401332
[Asm] 纯文本查看 复制代码
unsigned __int64 __fastcall sub_401332(__int64 a1)
{
_BYTE *v1; // r12
int v2; // ebx
char v3; // r13
const char *v4; // rax
signed int i; // [rsp+18h] [rbp-58h]
signed int j; // [rsp+1Ch] [rbp-54h]
char s[8]; // [rsp+20h] [rbp-50h]
__int64 v9; // [rsp+28h] [rbp-48h]
__int64 v10; // [rsp+30h] [rbp-40h]
__int64 v11; // [rsp+38h] [rbp-38h]
char v12; // [rsp+40h] [rbp-30h]
unsigned __int64 v13; // [rsp+48h] [rbp-28h]
v13 = __readfsqword(0x28u);
v12 = 0;
s[0] = -103;
s[1] = -80;
s[2] = -121;
s[3] = -98;
s[4] = 112;
s[5] = -24;
s[6] = 65;
s[7] = 68;
v9 = 0x5855BC749A8B0405LL;
v10 = -1920493130842480203LL;
v11 = -3031743088776258207LL;
for ( i = 0; i <= 3; ++i )
{
for ( j = 1; j < strlen(s); ++j )
{
v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, j);
v2 = *(unsigned __int8 *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
a1,
j);
v3 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, j - 1) | v2;
LOBYTE(v2) = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
a1,
j);
*v1 = v3 & ~(v2 & *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
a1,
j - 1));
}
}
v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(a1);
if ( strncmp(v4, s, 32uLL) == 0 )
sub_401522();
return __readfsqword(0x28u) ^ v13;
}
- 进入函数之后,先进行一系列的赋值操作,然后是一个大循环,循环次数为4次,在这个大循环中套着一个小循环,小循环的次数是我们字符串s的长度,经过在上一个check函数跳过的坑,所以我们对这一个check函数直接看汇编代码
- 程序先将a[j]取出来,再将a[j-1]|a[j],当我调试的时候发现第一次或运算的两个数是0xc0和0xc5,这个我就很是纳闷了,我之前输入的是012345....,怎么变成了0xc0、0xc5了呢,然后回头一想,我们的字符串在之前的check函数进行过一次处理
[Python] 纯文本查看 复制代码
str1 = "0123456789abcdef"
i=0
for x in str1:
print hex(((ord(x)<<2)|(ord(x)>>6))&0xff^i),
i+=1
out:0xc0 0xc5 0xca 0xcf 0xd4 0xd1 0xde 0xdb 0xe8 0xed 0x8f 0x82 0x81 0x9c 0x9b 0x96
- emmmmmmm,这就对上了,第一次“0xc5|0xc0=0xc5”,然后将计算的结果和a[j-1]进行&运算"0xc5 & 0xc0 = 0xc0 ",然后再将结果进去not(按位取反)“~0xc0”,然后和a[j]进行and操作,最后得到的结果为5,综上所述,这个多的运算其实是将a[j]=a[j]^a[j-1],验证一下:
[Python] 纯文本查看 复制代码
print 0xc0^0xc5
out:5
- 和刚刚的到的结果吻合,所以我们的推测是对的,根据刚刚的调试简化sub_401332这个函数
[Python] 纯文本查看 复制代码
for i in range(4):
for j in range(32):
a[j] = a[j]^a[j-1]
- 当循环结束后,把处理过的字符串和s进行比较,如果正确的话,就会输出“you got it!”,看来这才是真正的flag,脚本如下:
[Python] 纯文本查看 复制代码
list1 = [0x99,0xB0,0x87,0x9E,0x70,0xE8,0x41,0x44,5,4,0x8B,0x9A,0x74,0xBC,0x55,0x58,0xB5,0x61,0x8E,0x36,0xAC,9,0x59,0xE5,0x61,0xDD,0x3E,0x3F,0xB9,0x15,0xED,0xD5]
for x in range(4):
for x in range(31,0,-1):
list1[x] = list1[x]^list1[x-1]
flag = ""
for x in range(len(list1)):
flag+=chr(((list1[x]^x)<<6|(list1[x]^x)>>2)&0xff)
print flag
- 写这个脚本的时候我还是有点迷,迷在他们之间的关系,check2异或运算是从第2个开始的加密
[Asm] 纯文本查看 复制代码
加密:
a[0]= 4 a[1] = 5 a[2] = 6
a[1] = a[1]^a[0] = 5^4 = 1
a[2] = a[2]^a[1] = 6^1 = 7
解密:
a[2] = a[2]^a[1] = 7^1 = 6
a[1] = a[1]^a[0] = 1^4 = 5
What’s_it
- 该题目为32位PE文件,使用PEID检测发现没有加壳
- 拖入IDA中,查看main函数
[Asm] 纯文本查看 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int8 v4[4]; // [esp+1Ch] [ebp-48h]
unsigned __int8 v5[4]; // [esp+20h] [ebp-44h]
char v6[32]; // [esp+24h] [ebp-40h]
char v7; // [esp+44h] [ebp-20h]
char Str[4]; // [esp+45h] [ebp-1Fh]
__int16 v9; // [esp+49h] [ebp-1Bh]
char v10; // [esp+4Bh] [ebp-19h]
int k; // [esp+4Ch] [ebp-18h]
int j; // [esp+50h] [ebp-14h]
int i; // [esp+54h] [ebp-10h]
int v14; // [esp+58h] [ebp-Ch]
int v15; // [esp+5Ch] [ebp-8h]
__main();
*(_DWORD *)Str = 0;
v9 = 0;
v10 = 0;
memset(v6, 0, sizeof(v6));
v7 = 0;
*(_DWORD *)v5 = 0;
*(_DWORD *)v4 = 0;
v15 = 0;
v14 = 0;
printf("Please input your luck string:");
scanf("%s", Str);
if ( strlen(Str) != 6 )
return 0;
for ( i = 0; i <= 5; ++i )
{
if ( Str[i] <= '`' || Str[i] > 'z' ) // 限制string全部为小写
return 0;
}
getMD5(Str, v6); // v6是加密后的ozulmt
for ( j = 0; j <= 31; ++j )
{
if ( v6[j] == '0' )
{
++v15;
v14 += j;
}
}
if ( 10 * v15 + v14 == 403 )
{
for ( k = 0; k <= 3; ++k )
{
v5[k] = v6[k]; // v5是md5的前四位
v4[k] = v6[k + 28]; // v4是md5的后四位
}
decode(v4);
}
check(v5);
return 0;
}
- 该程序首先让你输入一个luck string,规定长度为6,必须全部是小写字母,并且md5加密后,0的个数和所在的下标满足“ 10 * 个数 + 下标和 == 403”
- 接下来就是用python写脚本进行爆破
[Python] 纯文本查看 复制代码 false[/color]]
# coding:utf-8
import hashlib
def md5encode(str1):
a = hashlib.md5()
a.update(str1)
return a.hexdigest()
def seek_luckstring():
string = ""
for a in range(97,123):
for b in range(97,123):
for c in range(97,123):
for d in range(97,123):
for e in range(97,123):
for f in range(97,123):
string = chr(a)+chr(b)+chr(c)+chr(d)+chr(e)+chr(f)
md5_string = md5encode(string)
v14=0
v15=0
for x in range(len(md5_string)):
if md5_string[x]=="0":
v15+=1
v14+=x
if 10*v15+v14==403:
print string
exit(0)
seek_luckstring()
- 最后得出luckstring是“ozulmt”
- 然后通过循环分别给v4,v5赋值def seek_v4v5():
[Python] 纯文本查看 复制代码
def seek_v4v5():
luckstring = md5encode("ozulmt")
print luckstring
v4 = [""]*4
v5 = [""]*4
for x in range(4):
v5[x] = luckstring[x]
v4[x] = luckstring[x+28]
print v4 #['0', '1', '0', 'e']后四位
print v5 #['0', 'e', 'c', '4']前四位
[Asm] 纯文本查看 复制代码
// write access to const memory has been detected, the output may be wrong!
signed int __cdecl decode(unsigned __int8 *a1)
{
signed int result; // eax
signed int j; // [esp+24h] [ebp-14h]
signed int i; // [esp+28h] [ebp-10h]
unsigned int Seed; // [esp+2Ch] [ebp-Ch]
Seed = 0;
for ( i = 0; i <= 3; ++i )
Seed += a1[i]; // seed = 246
srand(Seed);
*(_BYTE *)check ^= 0x96u; // check = 0xc3
for ( j = 1; ; ++j )
{
result = j;
if ( j >= 305 )
break;
*((_BYTE *)check + j) ^= rand();
}
return result;
}
- 这个函数是对check函数的字节码进行操作,将数组v4中的每一个值得ASCII码加起来作为srand的种子,然后将check的每一个字节码都与rand()产生的随机数进行异或
- 我们知道,当srand()的种子一样的情况下,生成的rand()随机值也是一样的,所以check函数是可以恢复的
- 我动态调试的时候,check函数确实恢复成功了
- 通过观察,我们可以知道这个函数首先让你输入一个flag,然后进入到checkht函数中
[Asm] 纯文本查看 复制代码
char *__cdecl checkht(char *Dest)
{
int v1; // eax
char Source[48]; // [esp+1Ch] [ebp-4Ch]
__int16 v4; // [esp+4Ch] [ebp-1Ch]
int v5; // [esp+4Eh] [ebp-1Ah]
__int16 v6; // [esp+52h] [ebp-16h]
int j; // [esp+54h] [ebp-14h]
int i; // [esp+58h] [ebp-10h]
int v9; // [esp+5Ch] [ebp-Ch]
v5 = 1734437990;
v6 = 123;
memset(Source, 0, sizeof(Source));
v4 = 0;
v9 = 5;
for ( i = 0; ; ++i )
{
v1 = strlens(Dest);
if ( v1 - 1 <= v9 )
break;
if ( Dest[v9] == '-' ) // 去掉我们输入字符串中的"-",新字符串存在source中
--i;
else
Source[i] = Dest[v9];
++v9;
}
for ( j = 0; j <= 4; ++j )
{
if ( Dest[j] != *((_BYTE *)&v5 + j) ) // 前5位位 "flag{"
{
printfs(0);
exit(0);
}
}
if ( Dest[13] != Dest[28] ) // Dest[13] == Dest[28]
{
printfs(0);
exit(0);
}
if ( Dest[13] != Dest[18] )
{
printfs(0);
exit(0);
}
if ( Dest[13] != Dest[23] )
{
printfs(0);
exit(0);
}
if ( Dest[13] != 45 )
{
printfs(0);
exit(0);
}
if ( Dest[41] != '}' )
{
printfs(0);
exit(0);
}
return strcpy(Dest, Source);
}
- 该函数首先把我们输入的字符串进行去除“-”,然后有几个判断,如果有一个不满足则会退出程序:
[Asm] 纯文本查看 复制代码
条件:
前5位位 "flag{"
Dest[13] == Dest[28] = "-"
Dest[13] == Dest[18] = "-"
Dest[13] == Dest[23] = "-"
Dest[13] == 45 = "-"
最后一个为'}'
- 最后将处理后的字符串返回,接着看check函数,又是利用随机值来生成一个正确的flag,饭后与我们的输入进行比较
[Python] 纯文本查看 复制代码
def check2():
# v5 = ['0', 'e', 'c', '4']
# v10 = 0
# for x in range(len(v5)):
# v10 += ord(v5[x])
# print v10
# v10 = 300
ASCII = "0123456789abcdef"
rand = [1018,20977,6537,12471,23035,10168,24596,6071,15959,21200,27017,8546,23669,16211,26282,7236,26183,16060,27924,26961,4987,23116,4119,365,8438,9389,31685,29746,25726,20182,17993,17389]
flag = ""
for x in range(len(rand)):
flag+=ASCII[rand[x]%16]
print flag
#a197b847709253a47c41bc7d6d52e69d
- 我们可以得到一串字符串a197b847709253a47c41bc7d6d52e69d,然后根据之前的那几个限制条件,得出最终的flag:flag{a197b847-7092-53a4-7c41-bc7d6d52e69d}
[Asm] 纯文本查看 复制代码
def check2():
# v5 = ['0', 'e', 'c', '4']
# v10 = 0
# for x in range(len(v5)):
# v10 += ord(v5[x])
# print v10
# v10 = 300
ASCII = "0123456789abcdef"
rand = [1018,20977,6537,12471,23035,10168,24596,6071,15959,21200,27017,8546,23669,16211,26282,7236,26183,16060,27924,26961,4987,23116,4119,365,8438,9389,31685,29746,25726,20182,17993,17389]
flag = ""
for x in range(len(rand)):
flag+=ASCII[rand[x]%16]
print flag
#a197b847709253a47c41bc7d6d52e69d
cyvm
- 同样拖入IDA中查看伪C代码,发现是循环里套switch,代码非常多,我还以为是算法题,然后我分析了好长时间没分析出来,根据官方WP,这道题是一个VM题
- WP上有一句话值得我记住,就是“while循环套switch,不是迷宫就是vm”,这也许就是经验只谈吧,我这个没经验的人看起来这个题是算法题emmmmmmm
- 判断是VM:1.看到这么多case,那一定是vm了 2.根据传入函数的a2和每次goto前的”+=”运算可以很容易的确定v5是读字节码的操作
- 根据WP分析出:
[Python] 纯文本查看 复制代码
s2reg 1
reg2s 2
mov 3
add 4
sub 5
xor 6
and 7
jmp 9
cmp 10
or 11
jnz 12
putc 13
ipt 15
movs 16
adds 17
inc 18
[Python] 纯文本查看 复制代码
input[i]=input[i+1]^i
[Python] 纯文本查看 复制代码
#coding:utf-8
s1 = [0x0A ,0x0C, 4,0x1F,0x48,0x5A,0x5F,3,0x62,0x67,0x0E,0x61,0x1E,0x19, 8,0x36,0x47,0x52,0x13,0x57,0x7C,0x39,0x54,0x4B, 5, 5,0x45,0x77,0x15,0x26,0x0E,0x62,0]
for x in range(31,-1,-1):
s1[x] ^= s1[x+1]^x
flag =""
for x in range(len(s1)):
flag+= chr(s1[x])
print flag
#flag{7h15_15_MY_f1rs7_s1mpl3_Vm}
|