Tags: selfmodifying 

Rating:

[Link to original writeup](https://wrecktheline.com/writeups/m0lecon-2021/#PeTaMorphosis)

# PeTaMorphosis (25 solves, 179 points)
by JaGoTu

```
Just another rev.

Author: a_sin
```

We get a `chall` file and an `out` file:

```
$ file chall
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a944a49767163c0ab20ac235146867045414c0d4, for GNU/Linux 3.2.0, stripped
$ file out
out: data
$ xxd out
00000000: bfb0 d0df 1068 feaa 8a5f b602 356a f049 .....h..._..5j.I
00000010: 488c 31d0 d2d8 b094 1eb8 0cbd 5854 15d6 H.1.........XT..
00000020: c80f 98e4 3d10 c0 ....=..
```

Let's start with static analysis:

```C++
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rbx
__int64 v4; // rax
int v5; // ebx
__int64 v6; // rax
_BYTE v8[512]; // [rsp-428h] [rbp-430h] BYREF
_QWORD v9[67]; // [rsp-228h] [rbp-230h] BYREF
__int64 v10; // [rsp-10h] [rbp-18h]

v10 = v3;
v9[65] = __readfsqword(0x28u);
std::ifstream::basic_ifstream(v9, "flag.txt", 8LL);
std::ofstream::basic_ofstream(v8, "out.txt", 16LL);
if ( (unsigned __int8)std::ifstream::is_open(v9) != 1 )
{
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Flag file 'flag.txt' not found");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
v5 = -1;
}
else
{
std::istream::read((std::istream *)v9, flagbuff, 39LL);
std::ifstream::close(v9);
if ( (unsigned int)unprotect_subs() == -1 )
{
v6 = std::operator<<<std::char_traits<char>>(&std::cout, "Something went wrong");
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
}
process_with_victs();
reorder_buff(flagbuff, 39);
modify_subs();
process_with_victs();
std::operator<<<std::char_traits<char>>(v8, flagbuff);
std::ofstream::close(v8);
v5 = 0;
}
std::ofstream::~ofstream(v8);
std::ifstream::~ifstream(v9);
return v5;
}
```

Apart from basic flag reading, the first suspicious sub is the `unprotect_subs()`:

```C
int __fastcall unprotect_subs()
{
int v1; // [rsp+Ch] [rbp-24h]

v1 = getpagesize();
if ( mprotect((char *)vict_1 - (unsigned __int64)vict_1 % v1, v1, 7) == -1 )
return -1;
if ( mprotect((char *)vict_2 - (unsigned __int64)vict_2 % v1, v1, 7) == -1 )
return -1;
if ( mprotect((char *)vict_3 - (unsigned __int64)vict_3 % v1, v1, 7) == -1 )
return -1;
return 0;
}
```

This looks like it's changing the protection of three "victim" procedures to be `rwx` instead of `rw-`, so we're dealing with a self-modifying binary.

Here's the python version of `process_with_vict()` looks like:

```python
for i in range(len(buff)):
mod = i % 3
curr = buff[i]
if mod == 2:
curr = vict_1(curr)
elif mod == 0:
curr = vict_2(curr)
else:
curr = vict_3(curr)
curr = vict_1(curr)
buff[i] = curr
```

`modify_subs()` uses static XOR keys to modify the vict subs to do something else:

```C
void __fastcall modify_subs()
{
unsigned __int8 v0; // [rsp+0h] [rbp-6h]
unsigned __int8 v1; // [rsp+1h] [rbp-5h]
unsigned __int8 v2; // [rsp+2h] [rbp-4h]
unsigned __int8 v3; // [rsp+3h] [rbp-3h]
unsigned __int8 v4; // [rsp+4h] [rbp-2h]
unsigned __int8 v5; // [rsp+5h] [rbp-1h]

v0 = 17;
v1 = 0;
while ( v0 <= 19u )
*((_BYTE *)vict_1 + v0++) ^= mod_1[v1++];
v2 = 17;
v3 = 0;
while ( v2 <= 19u )
*((_BYTE *)vict_2 + v2++) ^= mod_2[v3++];
v4 = 17;
v5 = 0;
while ( v4 <= 19u )
*((_BYTE *)vict_3 + v4++) ^= mod_3[v5++];
}
```

The `reorder_buff()` just does a shuffle of the buffer using a static array.

```C
void __fastcall reorder_buff(char *buff, int len)
{
int i; // [rsp+14h] [rbp-8h]
int j; // [rsp+18h] [rbp-4h]

for ( i = 0; i < len; ++i )
{
tmp_buff[reorder_array[i]] = buff[i];
buff[i] = 0;
}
for ( j = 0; j < len; ++j )
buff[j] = tmp_buff[j];
}
```

So our plan of attack is to create inverse functions for both the original vict functions and the modified vict functions, and an inverse for the reodering, and then run the output through them in reverse order.

To generate the modified functions I just use a small python script:

```python
with open('peta', 'rb') as f:
data = list(f.read())

mod1 = [0x42, 0x10, 0x98]
mod2 = [0x43, 0, 0x1C]
mod3 = [0, 0x18, 0x22]

for i in range(3):
data[0x17FD+17+i] ^= mod1[i]

for i in range(3):
data[0x1813+17+i] ^= mod2[i]

for i in range(3):
data[0x1829+17+i] ^= mod3[i]

with open('peta_mod', 'wb') as f:
f.write(bytes(data))
```

One thing to note: while implementing the inverse functions for victs, I made some mistakes. The provided binary has some anti-debugging, so I couldn't easily get it to run under gdb, and therefore couldn't dynamically see the processing result after each phase. There was no general modification protection though, therefore I decided to get interim debug outputs from the binary by noping out the calls one by one.

Apart from the modified `vict_1`, all of the functions were unambiguously invertible. The modified `vict_1` did `2*curr`, therefore losing 1 bit. I just created two outputs, one with the bit set and one without, and then took the bits that fitted better.

Here is the final python script:

```python
import binascii

with open('out', 'rb') as f:
data = f.read()

rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

or_80_arr = [0]*39

out1 = []

for i in range(len(data)):
mod = i % 3
if mod == 2:
out1.append(data[i]//2 | or_80_arr[i]) #v1
elif mod == 0:
out1.append(ror(data[i],5,8)) #v2
else:
tmp = data[i]//2 | or_80_arr[i] #v3
out1.append(tmp ^ 0x21) #v1

print(binascii.hexlify(bytes(out1)))


reord_array = [0x12, 0x02, 0x07, 0x0D, 0x14, 0x1D, 0x10, 0x09, 0x11, 0x1A, 0x05, 0x00, 0x1F, 0x0A, 0x26, 0x17,
0x23, 0x03, 0x21, 0x16, 0x0C, 0x19, 0x25, 0x15, 0x0F, 0x24, 0x06, 0x1C, 0x08, 0x22, 0x0B, 0x1E,
0x1B, 0x0E, 0x01, 0x04, 0x13, 0x18, 0x20]

out2 = []

for i in range(len(reord_array)):
out2.append(out1[reord_array[i]])

print(binascii.hexlify(bytes(out2)))

out3 = []

for i in range(len(out2)):
mod = i % 3
if mod == 2:
out3.append(out2[i] ^ 0x99) #v1
elif mod == 0:
out3.append((out2[i] -25) & 0xFF) #v2
else:
tmp = out2[i] ^ 0x99 #v3
out3.append((tmp+3) & 0xFF) #v1

print(binascii.hexlify(bytes(out3)))
print(bytes(out3))
```

Here are the two outputs:

```
$ python3 peta.py
b'fdf9e8fea9b4f7f4c5fafa81a994f84a85c689c9e9c6f9caf0fd86ed8daaa8cae478edf2e9a9e0'
b'89e8f494e9aa85fac686b4fdcafae0caf2fe78f9a9fda9c64ae9f78dc5ed81a8edf8f9a9c9f0e4'
b'70746d7b73336c665f6d3064b16679b16e675f6330e4335f31736e745f74683474df6330b06c7d'
b'ptm{s3lf_m0d\xb1fy\xb1ng_c0\xe43_1snt_th4t\xdfc0\xb0l}'
$ python3 peta.py
b'fd7968fe2934f77445fa7a01a914784a0546894969c6794af07d06ed0d2aa84a64786d72e92960'
b'89687414692a05fa460634fd4a7a604a72fe7879a97d29c64ae9f70d456d01a8ed78792949f064'
b'70f4edfbf3b3ec66dfedb06431e6f931ee675fe33064b35f31736ef4dff4e834745fe3b0306cfd'
b'p\xf4\xed\xfb\xf3\xb3\xecf\xdf\xed\xb0d1\xe6\xf91\xeeg_\xe30d\xb3_1sn\xf4\xdf\xf4\xe84t_\xe3\xb00l\xfd'
```

By combining them, we get the flag, which is `ptm{s3lf_m0d1fy1ng_c0d3_1snt_th4t_c00l}`

Original writeup (https://wrecktheline.com/writeups/m0lecon-2021/#PeTaMorphosis).