Try   HackMD

CTF Writeup - Move or not (BambooFox CTF)

View the book with " Book Mode".

Resources

First step

  • file
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

we see PIE executable, this means that

tags: reverse engineering obscure function PIE

Execute

parallels@parallels-vm:~/Desktop/MoveOrNot$ ./pro.dms First give me your password: 1 You don't know static analysis !

Firstly, it asks you to enter the password, if a wrong password is given, it mocks you and end.

parallels@parallels-vm:~/Desktop/MoveOrNot$ ./pro.dms First give me your password: 98416 Second give me your key: 50 Then Verify your flag: ? You don't know dynamic analysis !

Through, reverse engineering, we can easily figure out that the first password is 98416. After this, it will ask you to enter the second key, and ask you to verify your flag at the end.

Reverse

undefined8 main(void) { int iVar1; long in_FS_OFFSET; int option; int count; char try_flag [40]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); count = 0; printf("First give me your password: "); /* scanf("%d", an integer) */ scanf(&DAT_00100a06,&option); if (option != 98416) { puts("You don\'t know static analysis !"); /* WARNING: Subroutine does not return */ exit(0); } printf("Second give me your key: "); /* scanf("%d", an integer) */ scanf(&DAT_00100a06,&option); option = option + -49; count = 0; while (count < 0xc) { *(char *)(FUN_00301020 + count) = (char)FUN_00301020[count] + (char)option; count = count + 1; } FUN_00301020(&magicstring); printf("Then Verify your flag: "); scanf(&DAT_00100a63,try_flag); iVar1 = strcmp((char *)&magicstring,try_flag); if (iVar1 == 0) { puts("You are right. Congratulations !!"); } else { puts("You don\'t know dynamic analysis !"); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }

The code above has been sorted, so it should be easier to read.

Firstly, line 16 shows the first password.

Secondly, line 23 scans the second key with data type int.
The key will be updated by substracting 49.

Here comes the funniest part.

while (count < 0xc) { *(char *)(FUN_00301020 + count) = (char)FUN_00301020[count] + (char)option; count = count + 1; }

it modifies machine code somehow by add a certain value.

We left this behind for a second, and find that it call the modified function FUN_00301020(&magicstring);

ok so what we got there? FUN_00301020()

/* WARNING: Control flow encountered bad instruction data */

void FUN_00301020(int param_1,undefined8 param_2,undefined8 param_3,undefined4 *param_4)

{
  int *piVar1;
  int in_EAX;
  undefined4 in_register_00000004;
  char *magic_string;
  char unaff_R14B;
  bool in_ZF;
  char in_SF;
  char in_OF;
  
  if (!in_ZF && in_OF == in_SF) {
    piVar1 = (int *)(CONCAT44(in_register_00000004,in_EAX) + -0x7cb7c3f9);
    *piVar1 = *piVar1 + in_EAX;
    *param_4 = 0x48480780;
    magic_string = (char *)(ulong)(param_1 + 1);
    *magic_string = *magic_string + 'o';
    magic_string[1] = magic_string[1] + ';';
    magic_string[2] = magic_string[2] + 'Y';
    magic_string[3] = magic_string[3] + 'F';
    magic_string[4] = magic_string[4] + -0x14;
    magic_string[5] = magic_string[5] + '\b';
    magic_string[6] = magic_string[6] + '\x01';
    magic_string[7] = magic_string[7] + '1';
    magic_string[8] = magic_string[8] + '\x18';
    magic_string[9] = magic_string[9] + -9;
    magic_string[10] = magic_string[10] + '\x11';
    magic_string[0xb] = magic_string[0xb] + -5;
    magic_string[0xc] = magic_string[0xc] + -0x45;
    magic_string[0xd] = magic_string[0xd] + '\x1a';
    magic_string[0xe] = magic_string[0xe] + '-';
    magic_string[0xf] = magic_string[0xf] + -0xb;
    magic_string[0x10] = magic_string[0x10] + 'U';
    magic_string[0x11] = magic_string[0x11] + '\x18';
    magic_string[0x12] = magic_string[0x12] + '\x19';
    magic_string[0x13] = magic_string[0x13] + -0x3b;
    magic_string[0x14] = magic_string[0x14] + -9;
    magic_string[0x15] = magic_string[0x15] + '\b';
    magic_string[0x16] = magic_string[0x16] + 'X';
    return;
  }
  if (unaff_R14B == '\0' || SCARRY1(unaff_R14B,'\0') != unaff_R14B < '\0') {
                    /* WARNING: Bad instruction - Truncating control flow here */
    halt_baddata();
  }
                    /* WARNING: Bad instruction - Truncating control flow here */
  halt_baddata();
}

I was confused by this segment in the first place

piVar1 = (int *)(CONCAT44(in_register_00000004,in_EAX) + -0x7cb7c3f9); *piVar1 = *piVar1 + in_EAX; *param_4 = 0x48480780;

it turns out that we don't and should not read this code, I'll explain this later.

The rest codes are quite easy to understand though

magic_string = (char *)(ulong)(param_1 + 1); *magic_string = *magic_string + 'o'; magic_string[1] = magic_string[1] + ';'; magic_string[2] = magic_string[2] + 'Y'; magic_string[3] = magic_string[3] + 'F';

modify the magic_string

According to Ghidra the magic_string is at address 00301100

magicstring XREF[2]: main:001008d4(*), main:00100912(*) 00301100 69 db 69h 00301101 41 ?? 41h A 00301102 78 ?? 78h x 00301103 55 ?? 55h U 00301104 2e ?? 2Eh . 00301105 7c ?? 7Ch | 00301106 26 ?? 26h & 00301107 33 ?? 33h 3 00301108 30 ?? 30h 0 00301109 0c ?? 0Ch 0030110a 29 ?? 29h ) ...

OK. Let's pause and think
we got

  • magic_string: a raw string
  • FUN_00301020: modify magic_string somehow

Let's go back to FUN_00301020 and microscope it's assembly

00301020 7f 2e JG LAB_00301050 00301022 26 47 82 ADD R14B,0x0 c6 00 00301027 7f 06 JG LAB_0030102f 00301029 1f ?? 1Fh 0030102a 47 ?? 47h G 0030102b 82 ?? 82h 0030102c c7 ?? C7h 0030102d 01 ?? 01h 0030102e 80 ?? 80h LAB_0030102f XREF[1]: 00301027(j) 0030102f 2f ?? 2Fh / 00301030 0b ?? 0Bh 00301031 48 ?? 48h H 00301032 83 ?? 83h 00301033 c7 ?? C7h 00301034 01 ?? 01h 00301035 80 ?? 80h 00301036 07 ?? 07h 00301037 0d ?? 0Dh 00301038 48 ?? 48h H 00301039 83 ?? 83h 0030103a c7 ?? C7h 0030103b 01 ?? 01h 0030103c 80 ?? 80h 0030103d 07 ?? 07h 0030103e 41 ?? 41h A 0030103f 48 ?? 48h H 00301040 83 ?? 83h 00301041 c7 ?? C7h 00301042 01 ?? 01h 00301043 80 ?? 80h 00301044 2f ?? 2Fh / 00301045 0d ?? 0Dh 00301046 48 ?? 48h H 00301047 83 ?? 83h 00301048 c7 ?? C7h 00301049 01 ?? 01h 0030104a 80 ?? 80h 0030104b 07 ?? 07h 0030104c 20 ?? 20h 0030104d 48 ?? 48h H 0030104e 83 ?? 83h 0030104f c7 ?? C7h LAB_00301050 XREF[1]: 00301020(j) 00301050 01 80 07 ADD dword ptr [RAX + -0x7cb7c3f9],EAX 3c 48 83 00301056 c7 01 80 MOV dword ptr [RCX],0x48480780 07 48 48 0030105c 83 c7 01 ADD magic_string,0x1 0030105f 80 07 6f ADD byte ptr [magic_string],0x6f 00301062 48 83 c7 01 ADD magic_string,0x1 00301066 80 07 3b ADD byte ptr [magic_string],0x3b 00301069 48 83 c7 01 ADD magic_string,0x1 0030106d 80 07 59 ADD byte ptr [magic_string],0x59 00301070 48 83 c7 01 ADD magic_string,0x1 00301074 80 07 46 ADD byte ptr [magic_string],0x46 00301077 48 83 c7 01 ADD magic_string,0x1 0030107b 80 2f 14 SUB byte ptr [magic_string],0x14 0030107e 48 83 c7 01 ADD magic_string,0x1 00301082 80 07 08 ADD byte ptr [magic_string],0x8 00301085 48 83 c7 01 ADD magic_string,0x1 ...

Note that the first 49 lines are non-sense so it must be related to the obscure function mentioned above.

By analyzing the machine code below, get

examples:
83 c7 01        ADD        magic_string,0x1
80 07 6f        ADD        byte ptr [magic_string],0x6f
48 83 c7 01     ADD        magic_string,0x1
80 2f 14        SUB        byte ptr [magic_string],0x14


features:
83 c7 01
80 07 ()
48 83 c7 01
80 2f 14

Does the non-sense codes meet this features after some operations?

The answer is: Yes, plus 1

from 00301020:
(all plus 1)
80 2f 27    SUB 0x27
48 83 c7 01     ADD, magic_string, 0x1
80 07 20    ADD 0x20

from 0030102e:

80 2f 0b    SUB 0xb
48 83 c7 01     ADD, magic_string, 0x1
80 07 0d    ADD 0xd
48 83 c7 01     ADD, magic_string, 0x1
80 07 41    ADD 0x41
48 83 c7 01     ADD, magic_string, 0x1
80 2f 0d    SUB 0xd
48 83 c7 01     ADD, magic_string, 0x1
80 07 20    ADD 0x20
48 83 c7 01     ADD, magic_string, 0x1
80 07 3c    ADD 0x3c
48 83 c7 01     ADD, magic_string, 0x1
80 07 48    ADD 0x48

It's at this point that we realize that the

while (count < 0xc) { *(char *)(FUN_00301020 + count) = (char)FUN_00301020[count] + (char)option; count = count + 1; }

actually expect the option be 1(so we should enter 50).

Now we can come up with exploit.py

magic_string = [ 0x69, 0x41, 0x78, 0x55, 0x2E, 0x7C, 0x26, 0x33, 0x30, 0x0C, 0x29, 0x20, 0x28, 0x48, 0x65, 0x68, 0x32, 0x47, 0x3A, 0x62, 0x64, 0x79, 0x52, 0x46, 0x3B, 0x0A, 0x4F, 0x59, 0x6E, 0x3D, 0x6C, 0x25, 0x00, ] seasoning = [ -0x27, 0x20, -0xb, 0xd, 0x41, -0xd, 0x20, 0x3c, 0x48, 0x6f, 0x3b, 0x59, 0x46, -0x14, 0x8, 0x1, 0x31, 0x18, -0x9, 0x11, -0x5, -0x45, 0x1a, 0x2d, -0xb, 0x55, 0x18, 0x19, -0x3b, -0x9, 0x8, 0x58, ] """ 0030105f 80 07 6f ADD byte ptr [magic_string],0x6f 00301062 48 83 c7 01 ADD magic_string,0x1 00301066 80 07 3b ADD byte ptr [magic_string],0x3b 00301069 48 83 c7 01 ADD magic_string,0x1 0030106d 80 07 59 ADD byte ptr [magic_string],0x59 00301070 48 83 c7 01 ADD magic_string,0x1 00301074 80 07 46 ADD byte ptr [magic_string],0x46 00301077 48 83 c7 01 ADD magic_string,0x1 0030107b 80 2f 14 SUB byte ptr [magic_string],0x14 0030107e 48 83 c7 01 ADD magic_string,0x1 00301082 80 07 08 ADD byte ptr [magic_string],0x8 00301085 48 83 c7 01 ADD magic_string,0x1 00301089 80 07 01 ADD byte ptr [magic_string],0x1 0030108c 48 83 c7 01 ADD magic_string,0x1 00301090 80 07 31 ADD byte ptr [magic_string],0x31 00301093 48 83 c7 01 ADD magic_string,0x1 00301097 80 07 18 ADD byte ptr [magic_string],0x18 0030109a 48 83 c7 01 ADD magic_string,0x1 0030109e 80 2f 09 SUB byte ptr [magic_string],0x9 003010a1 48 83 c7 01 ADD magic_string,0x1 003010a5 80 07 11 ADD byte ptr [magic_string],0x11 003010a8 48 83 c7 01 ADD magic_string,0x1 003010ac 80 2f 05 SUB byte ptr [magic_string],0x5 003010af 48 83 c7 01 ADD magic_string,0x1 003010b3 80 2f 45 SUB byte ptr [magic_string],0x45 003010b6 48 83 c7 01 ADD magic_string,0x1 003010ba 80 07 1a ADD byte ptr [magic_string],0x1a 003010bd 48 83 c7 01 ADD magic_string,0x1 003010c1 80 07 2d ADD byte ptr [magic_string],0x2d 003010c4 48 83 c7 01 ADD magic_string,0x1 003010c8 80 2f 0b SUB byte ptr [magic_string],0xb 003010cb 48 83 c7 01 ADD magic_string,0x1 003010cf 80 07 55 ADD byte ptr [magic_string],0x55 003010d2 48 83 c7 01 ADD magic_string,0x1 003010d6 80 07 18 ADD byte ptr [magic_string],0x18 003010d9 48 83 c7 01 ADD magic_string,0x1 003010dd 80 07 19 ADD byte ptr [magic_string],0x19 003010e0 48 83 c7 01 ADD magic_string,0x1 003010e4 80 2f 3b SUB byte ptr [magic_string],0x3b 003010e7 48 83 c7 01 ADD magic_string,0x1 003010eb 80 2f 09 SUB byte ptr [magic_string],0x9 003010ee 48 83 c7 01 ADD magic_string,0x1 003010f2 80 07 08 ADD byte ptr [magic_string],0x8 003010f5 48 83 c7 01 ADD magic_string,0x1 003010f9 80 07 58 ADD byte ptr [magic_string],0x58 """ flags = [] for m, s in zip(magic_string, seasoning): flags.append(chr(m+s)) print(''.join(flags))
ztex@Ztexde-MBP  ~/Desktop/MoveOrNot  python exploit.py BambooFox{dyn4mic_1s_4ls0_gr34t}

Flag: BambooFox{dyn4mic_1s_4ls0_gr34t}

Discusion

We can also solve this by ltrace.

ltrace is a program that simply runs the specified command until it exits. It intercepts and records the dynamic library calls which are called by the executed process and the signals which are received by that process. It can also intercept and print the system calls executed by the program. Its use is very similar to strace(1). ltrace shows parameters of invoked functions and system calls. To determine what arguments each function has, it needs external declaration of function prototypes. Those are stored in files called prototype libraries--see ltrace.conf(5) for details on the syntax of these files. See the section PROTOTYPE LIBRARY DISCOVERY to learn how ltrace finds prototype libraries.
  • answer
parallels@parallels-vm:~/Desktop/MoveOrNot$ ltrace ./pro.dms printf("First give me your password: ") = 29 __isoc99_scanf(0x5578e53a3a06, 0x7fffd2555a38, 0x7f787d131780, 29First give me your password: 98416 ) = 1 printf("Second give me your key: ") = 25 __isoc99_scanf(0x5578e53a3a06, 0x7fffd2555a38, 0x7f787d131780, 25Second give me your key: 50 ) = 1 printf("Then Verify your flag: ") = 23 __isoc99_scanf(0x5578e53a3a63, 0x7fffd2555a40, 0x7f787d131780, 23Then Verify your flag: a ) = 1 strcmp("BambooFox{dyn4mic_1s_4ls0_gr34t}"..., "a") = -31 puts("You don't know dynamic analysis "...You don't know dynamic analysis ! ) = 34 +++ exited (status 0) +++

if the second key is wrong, crash

parallels@parallels-vm:~/Desktop/MoveOrNot$ ltrace ./pro.dms printf("First give me your password: ") = 29 __isoc99_scanf(0x55f18e10ba06, 0x7ffd7d515de8, 0x7fd811ecf780, 29First give me your password: 98416 ) = 1 printf("Second give me your key: ") = 25 __isoc99_scanf(0x55f18e10ba06, 0x7ffd7d515de8, 0x7fd811ecf780, 25Second give me your key: 1 ) = 1 --- SIGILL (Illegal instruction) --- +++ killed by SIGILL +++