alden.io

PancakesCon 3 CTF: Zorshift (300)

·5 mins

/img/pancakes-con-ctf-2022-zorshift.png

My writeup for the challenge Zorshift from PancakesCon3 CTF!

Summary #

This challenge was by far my favorite from this years PancakesCon CTF. It was a Rust, IBM S/390 binary which spat out an encrypted flag. I don’t frequently work with rust binaries, especially ones compiled for such an esoteric architecture.

Anyways, thanks again to the organizers and onto the writeup! :)

Preliminary Analysis #

The first step as always is to figure out what we’re working with. File tells us the following:

crackme: ELF 64-bit MSB pie executable, IBM S/390, version 1 (SYSV), dynamically linked, interpreter /lib/ld64.so.1, for GNU/Linux 3.2.0, not stripped

Great! It’s a 64-bit ELF for… IBM S/390? Strings tells us some other intersting stuff like:

String output why cool
src/main.rs ok cool it’s a rust bin
got zee flag, time to encrypt zee flag to keep it from prying eyez grabs the flag and then encrypts?

But nothing earth shattering.

The normal next step would usually be disassemble the code, however this was more difficult than expected. For some reason, I couldn’t find S/390 support in Ghidra, Binja, or r2. I figured my best bet was to just attach a debugger and try that way.


Setting up S/390 Environment #

There is probably a better way to do this, however it worked for me :)

When it comes to weird architectures, the first place I look is always QEMU. Sure enough, QEMU has support for s390! However setting up a VM with QEMU can be kind of a pain so I looked for an easier solution. This is where Docker and it’s multi-CPU architecture support comes in!

Checking on docker hub, we can see that tons of linux flavors all support IBM Z. Not only that, but when you run the container, docker handles the emulation bits! (For help setting up docker refer to their docs)

To grab an image from the docker hub use the following command:

docker pull s390x/ubuntu

Now that we’ve downloaded the image, we should just be able to run the container and have ourselves an s390 system!

➜ docker run -it s390x/ubuntu
WARNING: The requested image's platform (linux/s390x) does not match the detected host platform (linux/amd64) and no specific platform was requested
[email protected]:/# uname -a 
Linux c72d2c635907 5.15.11-76051511-generic #202112220937~1640185481~21.10~b3a2c21 SMP Wed Dec 22 15:41:49 U s390x s390x s390x GNU/Linux

Cool! Now let’s install some tools to help us with the analysis!

[email protected]:/# apt install binutils gdb vim -y 

And lastly, lets add the binary into the docker container:

➜  zorshift $ docker ps 
CONTAINER ID   IMAGE          COMMAND   CREATED          STATUS          PORTS     NAMES
c72d2c635907   s390x/ubuntu   "bash"    11 minutes ago   Up 11 minutes             infallible_aryabhata
➜  zorshift $ docker cp crackme infallible_aryabhata:/home

Now we should be all set to get into some reversing!


Reversing crackme #

Since we can finally run the binary, let’s give that a shot and see what happens!

output-from-crackme

main #

Wooooo! We finally have output: a happy little crusty and our encrypted flag.

Next, let’s throw it into GDB and see what’s actually going on. Since the binary isn’t stripped we’re able to find main no problemo. It just makes a call to crackme::main.

0x3a36 <+6>:    aghi   %r15,-168
0x3a3a <+10>:   lgr    %r5,%r3
0x3a3e <+14>:   lgfr   %r4,%r2
0x3a42 <+18>:   larl   %r0,0x37b0 <_ZN7crackme4main17h9ea2b39756731bddE>
0x3a48 <+24>:   stg    %r0,160(%r15)
0x3a4e <+30>:   la     %r2,160(%r15)

crackme::main #

The assembly for this function is super long so I’m not going to include the comlete thing. If you’re interested, check out the git repo.

There are 4 calls to crackme::xor_pass here, which is most likely setting up some encryption stuff. I didn’t end up actually looking at this because you can just skip the encryption completely

x3822 <+114>:   brasl   %r14,0x3500 <_ZN7crackme8xor_pass17h2fc787ed7e0bdf0eE>                                                                                                                                                            
0x3828 <+120>:   la      %r2,168(%r15)                                                                                                                                                                                                     
0x382c <+124>:   la      %r3,164(%r15)                                                                                                                                                                                                     
0x3830 <+128>:   brasl   %r14,0x3500 <_ZN7crackme8xor_pass17h2fc787ed7e0bdf0eE>                                                                                                                                                            
0x3836 <+134>:   la      %r2,168(%r15)                                                                                                                                                                                                     
0x383a <+138>:   la      %r3,164(%r15)                                                                                                                                                                                                     
0x383e <+142>:   brasl   %r14,0x3500 <_ZN7crackme8xor_pass17h2fc787ed7e0bdf0eE>                                                                                                                                                            
0x3844 <+148>:   la      %r2,168(%r15)                                                                                                                                                                                                     
0x3848 <+152>:   la      %r3,164(%r15)                                                                                                                                                                                                     
0x384c <+156>:   brasl   %r14,0x3500 <_ZN7crackme8xor_pass17h2fc787ed7e0bdf0eE>                                                                                                                                                            

This is the part to pay attention to. There is a call to crackme::encrypt. We know the program has the flag in an unencrypted form at somepoint. The highlighted line is responsible for encrypting it. We should be able to just NOP out the call to the encryption function and win!

0x38d8 <+296>:   la      %r3,200(%r15)
0x38dc <+300>:   brasl   %r14,0x3540 <_ZN7crackme7encrypt17h2f77433b43f57430E>
0x38e2 <+306>:   larl    %r0,0x14300
0x38e8 <+312>:   stg     %r0,296(%r15)
0x38ee <+318>:   mvghi   304(%r15),1

Patching the Binary #

Now, this is an incredibly hacky way to do the binary patching. It’s sort of necessary since we are running in a less than ideal environment. Below is a little flow chart of the process.

graph LR; A[Find bytes to patch out]-->B[xxd crackme]; B-->C[replce bytes with NOPs]; C-->D[xxd -r crackme.xxd]; D-->E[win!];

Step 1: Find bytes to replace #

We can see that at 0x38dc contains the branch we need to skip.

    [trimmed]
    38d4:       41 20 f0 a8        la      %r2,168(%r15)
    38d8:       41 30 f0 c8        la      %r3,200(%r15)
    38dc:       c0 e5 ff ff fe 32  brasl   %r14,3540 <_ZN7crackme7encrypt17h2f77433b43f57430E>
    38e2:       c0 00 00 00 85 0f  larl    %r0,14300 <__DTOR_END__+0x60>
    38e8:       e3 00 f1 28 00 24  stg     %r0,296(%r15)
    [trimmed]
    3a2c:       07 07              nopr    %r7

I’ve also included the last line for reference sake. We know that the bytes for a NOP are going to be 0707.

Step 2: XXD the binary #

Now we need to get the binary into a form in which we can easily patch. I used xxd however any hex editor works.

[email protected]:/home# xxd crackme > crackme-original.hex

Step 3: Replace the branch with NOPS #

Here is the original section of hex, remember from before c0 e5 ff... is what we’re looking for.

000038c0: 41d0 d001 a708 0001 a701 0001 a774 ffe8  A............t..
000038d0: a7f4 ffee 4120 f0a8 4130 f0c8 c0e5 ffff  ....A ..A0......
000038e0: fe32 c000 0000 850f e300 f128 0024 e548  .2.........(.$.H

Here’s what it looks like afterwards.

000038c0: 41d0 d001 a708 0001 a701 0001 a774 ffe8  A............t..
000038d0: a7f4 ffee 4120 f0a8 4130 f0c8 [0707 0707  ....A ..A0......
000038e0: 0707 c000 0000 850f e300 f128 0024 e548  .2.........(.$.H

Step 4: Revert to executable #

[email protected]:/home# xxd -r crackme-original.hex > patched    
[email protected]:/home# chmod +x patched

Step 5: Win! #

At first glance, it doesn’t look like anything changed. However, we can see that the hex is slightly different!

patched

Decoding the hex gives us the flag:

[email protected]:/home# echo 50414e43414b45537b7a6f7273686966745f697a5f6e6f745f7a65637572657d | xxd -p -r 
PANCAKES{zorshift_iz_not_zecure}

And we’re done!