Skip to content

Latest commit

 

History

History

not_malware

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

not_malware

Description

Category: Reversing

Points: 150

To be perfectly frank, I do some malware-y things, but that doesn't mean that I'm actually malware, I promise!

nc rev.chal.csaw.io 5008

Analysis

To start with, it's a 64-bit binary that takes a single input via STDIN:

kali@kali:~/Downloads/csaw$ file not_malware
not_malware: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=68fd0eecf167f1c5992efffe7855c67a306aee33, for GNU/Linux 3.2.0, stripped
kali@kali:~/Downloads/csaw$ ./not_malware
What's your credit card number (for safekeeping) ?
>> hello
kali@kali:~/Downloads/csaw$ echo $?
1

Decompile it with Ghidra. Here's our entry routine:

void entry(undefined8 param_1,undefined8 param_2,undefined8 param_3)

{
  undefined8 in_stack_00000000;
  undefined auStack8 [8];
  
  __libc_start_main(FUN_001012bc,in_stack_00000000,&stack0x00000008,&LAB_001018f0,&DAT_00101960,
                    param_3,auStack8);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

And that invokes FUN_001012bc:

undefined8 FUN_001012bc(void)

{
  int iVar1;
  size_t sVar2;
  undefined8 uVar3;
  double dVar4;
  char local_b6 [10];
  undefined4 local_ac;
  char local_a8 [32];
  char local_88 [4];
  char local_84;
  char local_83;
  char local_82;
  char local_81;
  char local_80;
  char local_7f;
  char local_7e;
  char local_7d;
  char local_7c;
  char local_7b;
  char local_7a;
  char local_79;
  char local_78;
  char local_77;
  char local_76;
  char local_75;
  char local_68 [8];
  char local_60;
  char local_5f;
  char local_5e;
  char local_5d;
  char local_5c;
  char acStack91 [20];
  char local_47;
  char acStack70 [30];
  char local_28 [8];
  int local_20;
  int local_1c;
  int local_18;
  int local_14;
  uint local_10;
  int local_c;
  
  FUN_001012a6();
  printf("What\'s your credit card number (for safekeeping) ?\n>> ");
  fgets(local_68,0x3c,stdin);
  sVar2 = strlen(local_68);
  if (0x3c < sVar2) {
    puts("Well this was unnecessary.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_18 = 0x10;
  dVar4 = pow(16.00000000,0.50000000);
  local_18 = (int)(dVar4 - 1.00000000);
  local_c = 0;
  while (local_c < 8) {
    local_28[local_c] = local_68[local_c];
    local_c = local_c + 1;
  }
  local_28[local_c] = '\0';
  iVar1 = strncmp(local_28,"yeetbank" + (long)local_18 * 9,8);
  if (iVar1 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_60 != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_10 = (int)local_5f - 0x30;
  local_1c = (int)local_5e + -0x30;
  local_20 = (int)local_5d + -0x30;
  if (local_5c != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_14 = 0;
  while (local_14 < 0x14) {
    uVar3 = FUN_00101288((ulong)local_10);
    snprintf(local_b6,10,"%ld",uVar3);
    local_88[local_14] = local_b6[local_20];
    local_10 = local_10 + local_1c;
    local_14 = local_14 + 1;
  }
  local_c = 0;
  while (local_c < 0x14) {
    local_a8[local_c] = local_68[local_c + 0xd];
    local_c = local_c + 1;
  }
  if (local_a8[0] != local_88[0]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[16] != local_78) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[11] != local_7d) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[3] != local_88[3]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[7] != local_81) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[15] != local_79) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[1] != local_88[1]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[12] != local_7c) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[19] != local_75) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[13] != local_7b) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[14] != local_7a) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[5] != local_83) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[9] != local_7f) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[8] != local_80) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[18] != local_76) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[6] != local_82) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[17] != local_77) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[2] != local_88[2]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[10] != local_7e) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[4] != local_84) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_47 != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_c = 0;
  local_ac = 0x646e65;
  while( true ) {
    if (2 < local_c) {
      puts("Thanks!");
      FUN_00101229();
      return 0;
    }
    if (*(char *)((long)&local_ac + (long)local_c) != local_68[local_c + 0x22]) break;
    local_c = local_c + 1;
  }
                    /* WARNING: Subroutine does not return */
  exit(1);
}

Let's break it down. First we call FUN_001012a6 and get the input from STDIN:

  FUN_001012a6();
  printf("What\'s your credit card number (for safekeeping) ?\n>> ");
  fgets(local_68,0x3c,stdin);
  sVar2 = strlen(local_68);
  if (0x3c < sVar2) {
    puts("Well this was unnecessary.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }

FUN_001012a6 is basically an initialization function of sorts:

void FUN_001012a6(void)

{
  FUN_00101782();
  FUN_0010186e();
  FUN_001018b4();
  return;
}

The first one appears to set some variables based on which HV is in use:

void FUN_00101782(void)

{
  int iVar1;
  long lVar2;
  byte *pbVar3;
  byte *pbVar4;
  bool bVar5;
  bool bVar6;
  byte bVar7;
  byte local_19 [4];
  undefined local_15 [4];
  undefined local_11 [4];
  undefined local_d;
  undefined4 local_c;
  
  bVar7 = 0;
  local_c = 0;
  local_d = 0;
  FUN_0010173a(&local_c,local_19,local_11,local_15);
  iVar1 = strncmp((char *)local_19,"VMwareVMware",0xc);
  bVar5 = false;
  bVar6 = iVar1 == 0;
  if (!bVar6) {
    lVar2 = 10;
    pbVar3 = local_19;
    pbVar4 = (byte *)"KVMKVMKVM";
    do {
      if (lVar2 == 0) break;
      lVar2 = lVar2 + -1;
      bVar5 = *pbVar3 < *pbVar4;
      bVar6 = *pbVar3 == *pbVar4;
      pbVar3 = pbVar3 + (ulong)bVar7 * -2 + 1;
      pbVar4 = pbVar4 + (ulong)bVar7 * -2 + 1;
    } while (bVar6);
    if ((!bVar5 && !bVar6) != bVar5) {
      iVar1 = strncmp((char *)local_19,"TCGTCGTCGTCG",0xc);
      if (iVar1 != 0) {
        iVar1 = strncmp((char *)local_19,"Microsoft Hv",0xc);
        bVar5 = false;
        bVar6 = iVar1 == 0;
        if (!bVar6) {
          lVar2 = 0xc;
          pbVar3 = local_19;
          pbVar4 = (byte *)" lrpepyh vr";
          do {
            if (lVar2 == 0) break;
            lVar2 = lVar2 + -1;
            bVar5 = *pbVar3 < *pbVar4;
            bVar6 = *pbVar3 == *pbVar4;
            pbVar3 = pbVar3 + (ulong)bVar7 * -2 + 1;
            pbVar4 = pbVar4 + (ulong)bVar7 * -2 + 1;
          } while (bVar6);
          if ((!bVar5 && !bVar6) != bVar5) {
            return;
          }
        }
      }
    }
  }
                    /* WARNING: Subroutine does not return */
  exit(1);
}

Let's not follow that rabbit hole just yet.

FUN_0010186e throws an error if LD_PRELOAD or /etc/ld.so.preload are used:

void FUN_0010186e(void)

{
  int iVar1;
  char *pcVar2;
  
  pcVar2 = getenv("LD_PRELOAD");
  if (pcVar2 != (char *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  iVar1 = open("/etc/ld.so.preload",0);
  if (0 < iVar1) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  return;
}

And FUN_001018b4 calls ptrace to exit if we're trying to debug the binary:

void FUN_001018b4(void)

{
  long lVar1;
  
  lVar1 = ptrace(PTRACE_TRACEME,0,1,0);
  if (lVar1 == -1) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  return;
}

That ptrace call is where it fails if I try to use strace for example:

kali@kali:~/Downloads/csaw$ strace ./not_malware
execve("./not_malware", ["./not_malware"], 0x7ffc8c717550 /* 44 vars */) = 0
brk(NULL)                               = 0x559d8011a000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=118167, ...}) = 0
mmap(NULL, 118167, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffa71b72000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\362\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=1321344, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffa71b70000
mmap(NULL, 1323280, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffa71a2c000
mmap(0x7ffa71a3b000, 630784, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xf000) = 0x7ffa71a3b000
mmap(0x7ffa71ad5000, 626688, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa9000) = 0x7ffa71ad5000
mmap(0x7ffa71b6e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x141000) = 0x7ffa71b6e000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0n\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1839792, ...}) = 0
mmap(NULL, 1852680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffa71867000
mprotect(0x7ffa7188c000, 1662976, PROT_NONE) = 0
mmap(0x7ffa7188c000, 1355776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7ffa7188c000
mmap(0x7ffa719d7000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x170000) = 0x7ffa719d7000
mmap(0x7ffa71a22000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7ffa71a22000
mmap(0x7ffa71a28000, 13576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffa71a28000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffa71864000
arch_prctl(ARCH_SET_FS, 0x7ffa71864740) = 0
mprotect(0x7ffa71a22000, 12288, PROT_READ) = 0
mprotect(0x7ffa71b6e000, 4096, PROT_READ) = 0
mprotect(0x559d7f30c000, 4096, PROT_READ) = 0
mprotect(0x7ffa71bb9000, 4096, PROT_READ) = 0
munmap(0x7ffa71b72000, 118167)          = 0
openat(AT_FDCWD, "/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory)
ptrace(PTRACE_TRACEME)                  = -1 EPERM (Operation not permitted)
exit_group(1)                           = ?
+++ exited with 1 +++

We'll have to patch out that ptrace call to do any debugging.

#!/bin/env python3
from pwn import *
binary = ELF('./not_malware')
context.update(arch='amd64',os='linux')
print(binary.path)

addr1 = 0x18d1
print(binary.disasm(addr1, 40))
binary.asm(addr1,'''
nop
nop
nop
nop
nop
''')
patched = binary.path + '_patched'
print(patched)
print(binary.disasm(addr1, 40))
binary.save(patched)
os.system('chmod +x ' + patched)
kali@kali:~/Downloads/csaw$ ./patch_malware.py 
[*] '/home/kali/Downloads/csaw/not_malware'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
/home/kali/Downloads/csaw/not_malware
    18d1:       e8 0a f8 ff ff          call   0x10e0
    18d6:       48 83 f8 ff             cmp    rax, 0xffffffffffffffff
    18da:       75 0a                   jne    0x18e6
    18dc:       bf 01 00 00 00          mov    edi, 0x1
    18e1:       e8 2a f8 ff ff          call   0x1110
    18e6:       90                      nop
    18e7:       5d                      pop    rbp
    18e8:       c3                      ret    
    18e9:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
    18f0:       f3 0f 1e fa             endbr64 
    18f4:       41 57                   push   r15
    18f6:       4c                      rex.WR
    18f7:       8d                      .byte 0x8d
    18f8:       3d                      .byte 0x3d
/home/kali/Downloads/csaw/not_malware_patched
    18d1:       90                      nop
    18d2:       90                      nop
    18d3:       90                      nop
    18d4:       90                      nop
    18d5:       90                      nop
    18d6:       48 83 f8 ff             cmp    rax, 0xffffffffffffffff
    18da:       75 0a                   jne    0x18e6
    18dc:       bf 01 00 00 00          mov    edi, 0x1
    18e1:       e8 2a f8 ff ff          call   0x1110
    18e6:       90                      nop
    18e7:       5d                      pop    rbp
    18e8:       c3                      ret    
    18e9:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
    18f0:       f3 0f 1e fa             endbr64 
    18f4:       41 57                   push   r15
    18f6:       4c                      rex.WR
    18f7:       8d                      .byte 0x8d
    18f8:       3d                      .byte 0x3d

That's better, now we can trace it and get to the part where it prompts for user input:

kali@kali:~/Downloads/csaw$ strace ./not_malware_patched 
execve("./not_malware_patched", ["./not_malware_patched"], 0x7ffeafd53b20 /* 44 vars */) = 0
brk(NULL)                               = 0x56309109b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=118167, ...}) = 0
mmap(NULL, 118167, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff2a1f79000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\362\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=1321344, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff2a1f77000
mmap(NULL, 1323280, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ff2a1e33000
mmap(0x7ff2a1e42000, 630784, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xf000) = 0x7ff2a1e42000
mmap(0x7ff2a1edc000, 626688, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa9000) = 0x7ff2a1edc000
mmap(0x7ff2a1f75000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x141000) = 0x7ff2a1f75000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0n\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1839792, ...}) = 0
mmap(NULL, 1852680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ff2a1c6e000
mprotect(0x7ff2a1c93000, 1662976, PROT_NONE) = 0
mmap(0x7ff2a1c93000, 1355776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7ff2a1c93000
mmap(0x7ff2a1dde000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x170000) = 0x7ff2a1dde000
mmap(0x7ff2a1e29000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7ff2a1e29000
mmap(0x7ff2a1e2f000, 13576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ff2a1e2f000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff2a1c6b000
arch_prctl(ARCH_SET_FS, 0x7ff2a1c6b740) = 0
mprotect(0x7ff2a1e29000, 12288, PROT_READ) = 0
mprotect(0x7ff2a1f75000, 4096, PROT_READ) = 0
mprotect(0x563090829000, 4096, PROT_READ) = 0
mprotect(0x7ff2a1fc0000, 4096, PROT_READ) = 0
munmap(0x7ff2a1f79000, 118167)          = 0
openat(AT_FDCWD, "/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x5), ...}) = 0
brk(NULL)                               = 0x56309109b000
brk(0x5630910bc000)                     = 0x5630910bc000
write(1, "What's your credit card number ("..., 51What's your credit card number (for safekeeping) ?
) = 51
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x5), ...}) = 0
write(1, ">> ", 3>> )                      = 3
read(0, 

Back to FUN_001012bc, after the init routines and prompt for input:

  local_18 = 0x10;
  dVar4 = pow(16.00000000,0.50000000);
  local_18 = (int)(dVar4 - 1.00000000);
  local_c = 0;
  while (local_c < 8) {
    local_28[local_c] = local_68[local_c];
    local_c = local_c + 1;
  }
  local_28[local_c] = '\0';
  iVar1 = strncmp(local_28,"yeetbank" + (long)local_18 * 9,8);
  if (iVar1 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_60 != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }

That expects a certain 8 char prefix, followed by :. Let's set a breakpoint on that strncmp and try some bogus input:

        001013ca e8 71 fc        CALL       strncmp                                          int strncmp(char * __s1, char * 
                 ff ff
gef➤  r
Starting program: /home/kali/Downloads/csaw/not_malware_patched
What's your credit card number (for safekeeping) ?
>> ^C
Program received signal SIGINT, Interrupt.
...
gef➤  break *0x555555454000 + 0x001013ca
Breakpoint 1 at 0x5555555553ca
gef➤  c
Continuing.
hello
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdcd0  →  0x00000a6f6c6c6568 ("hello\n"?)
$rbx   : 0x0               
$rcx   : 0x000055555555603b  →  "softbank"
$rdx   : 0x8               
$rsp   : 0x00007fffffffdc40  →  0x0000000000000000
$rbp   : 0x00007fffffffdcf0  →  0x00005555555558f0  →   endbr64 
$rsi   : 0x000055555555603b  →  "softbank"
$rdi   : 0x00007fffffffdcd0  →  0x00000a6f6c6c6568 ("hello\n"?)
$rip   : 0x00005555555553ca  →   call 0x555555555040 <strncmp@plt>
$r8    : 0x00007ffff7f49b80  →  0x40671547652b82fe
$r9    : 0xffffffff00000000
$r10   : 0xfffffffffffff51c
$r11   : 0x00007ffff7ead020  →  <powf64+0> sub rsp, 0x18
$r12   : 0x0000555555555130  →   endbr64 
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdc40│+0x0000: 0x0000000000000000   ← $rsp
0x00007fffffffdc48│+0x0008: 0x0000000000000000
0x00007fffffffdc50│+0x0010: 0x0000000000000000
0x00007fffffffdc58│+0x0018: 0x0000000000000000
0x00007fffffffdc60│+0x0020: 0x0000000000000000
0x00007fffffffdc68│+0x0028: 0x0000000000000000
0x00007fffffffdc70│+0x0030: 0x0000000000000000
0x00007fffffffdc78│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x5555555553bf                  mov    edx, 0x8
   0x5555555553c4                  mov    rsi, rcx
   0x5555555553c7                  mov    rdi, rax
 → 0x5555555553ca                  call   0x555555555040 <strncmp@plt>
   ↳  0x555555555040 <strncmp@plt+0>  jmp    QWORD PTR [rip+0x2fda]        # 0x555555558020 <strncmp@got.plt>
      0x555555555046 <strncmp@plt+6>  push   0x1
      0x55555555504b <strncmp@plt+11> jmp    0x555555555020
      0x555555555050 <__isoc99_fscanf@plt+0> jmp    QWORD PTR [rip+0x2fd2]        # 0x555555558028 <__isoc99_fscanf@got.plt>
      0x555555555056 <__isoc99_fscanf@plt+6> push   0x2
      0x55555555505b <__isoc99_fscanf@plt+11> jmp    0x555555555020
─────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
strncmp@plt (
   $rdi = 0x00007fffffffdcd0 → 0x00000a6f6c6c6568 ("hello\n"?),
   $rsi = 0x000055555555603b → "softbank",
   $rdx = 0x0000000000000008,
   $rcx = 0x000055555555603b → "softbank"
)
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "not_malware_pat", stopped 0x5555555553ca in ?? (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555553ca → call 0x555555555040 <strncmp@plt>
[#1] 0x7ffff7ccccca → __libc_start_main(main=0x5555555552bc, argc=0x1, argv=0x7fffffffdde8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffddd8)
[#2] 0x55555555515e → hlt 
────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ 

Check the arguments to strncmp above. We're comparing the first 8 chars to softbank, which means the first 9 chars of the input string need to be:

softbank:

After that, those first 9 chars, there are 3 more expected followed by another : char:

  local_10 = (int)local_5f - 0x30;
  local_1c = (int)local_5e + -0x30;
  local_20 = (int)local_5d + -0x30;
  if (local_5c != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }

Subtracting 0x30 from an ASCII character value can be used to convert a digit into its numerical form. '0' (the character) has a hex value of 0x30 and '4' has a hex value of 0x34 for example. So '4' - 0x30 will give you 4.

After that, there are a couple of while loops doing some basic transformations:

  local_14 = 0;
  while (local_14 < 0x14) {
    uVar3 = FUN_00101288((ulong)local_10);
    snprintf(local_b6,10,"%ld",uVar3);
    local_88[local_14] = local_b6[local_20];
    local_10 = local_10 + local_1c;
    local_14 = local_14 + 1;
  }
  local_c = 0;
  while (local_c < 0x14) {
    local_a8[local_c] = local_68[local_c + 0xd];
    local_c = local_c + 1;
  }

And then we have a long series of char comparisons:

  if (local_a8[0] != local_88[0]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[16] != local_78) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[11] != local_7d) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[3] != local_88[3]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[7] != local_81) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[15] != local_79) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[1] != local_88[1]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[12] != local_7c) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[19] != local_75) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[13] != local_7b) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[14] != local_7a) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[5] != local_83) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[9] != local_7f) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[8] != local_80) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[18] != local_76) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[6] != local_82) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[17] != local_77) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[2] != local_88[2]) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[10] != local_7e) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  if (local_a8[4] != local_84) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }

Let's set our next breakpoint on the first of those comparisons, immediately after the while loops:

  if (local_a8[0] != local_88[0]) {
        001014d6 38 c2           CMP        DL,AL
        001014d8 74 0a           JZ         LAB_001014e4
gef➤  break *0x555555454000 + 0x001014d6
Breakpoint 2 at 0x5555555554d6

Run it again with new input:

gef➤  r
Starting program: /home/kali/Downloads/csaw/not_malware_patched 
What's your credit card number (for safekeeping) ?
>> softbank:AAA:BBBB

Continue past the first breakpoint and this is what we hit:

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x42              
$rsp   : 0x00007fffffffdc40  →  0x3834373939360000
$rbp   : 0x00007fffffffdcf0  →  0x00005555555558f0  →   endbr64 
$rsi   : 0x00005555555560c6  →  0x21736b6e61685400
$rdi   : 0x00007fffffffd9e0  →  0x00000000fbad8001
$rip   : 0x00005555555554d6  →   cmp dl, al
$r8    : 0x0               
$r9    : 0x00007fffffffdad0  →  0x0000000000000000
$r10   : 0x00007fffffffd97f  →  "699748423"
$r11   : 0x0               
$r12   : 0x0000555555555130  →   endbr64 
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdc40│+0x0000: 0x3834373939360000   ← $rsp
0x00007fffffffdc48│+0x0008: 0x0000000000333234 ("423"?)
0x00007fffffffdc50│+0x0010: 0x0000000a42424242 ("BBBB\n"?)
0x00007fffffffdc58│+0x0018: 0x0000000000000000
0x00007fffffffdc60│+0x0020: 0x0000000040000000
0x00007fffffffdc68│+0x0028: 0x0000000000000000
0x00007fffffffdc70│+0x0030: 0x0000000000000000
0x00007fffffffdc78│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x5555555554c8                  adc    edi, DWORD PTR [rsi-0x23]
   0x5555555554cb                  movzx  edx, BYTE PTR [rbp-0xa0]
   0x5555555554d2                  movzx  eax, BYTE PTR [rbp-0x80]
 → 0x5555555554d6                  cmp    dl, al
   0x5555555554d8                  je     0x5555555554e4
   0x5555555554da                  mov    edi, 0x1
   0x5555555554df                  call   0x555555555110 <exit@plt>
   0x5555555554e4                  movzx  edx, BYTE PTR [rbp-0x90]
   0x5555555554eb                  movzx  eax, BYTE PTR [rbp-0x70]
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "not_malware_pat", stopped 0x5555555554d6 in ?? (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555554d6 → cmp dl, al
[#1] 0x7ffff7ccccca → __libc_start_main(main=0x5555555552bc, argc=0x1, argv=0x7fffffffdde8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffddd8)
[#2] 0x55555555515e → hlt 
────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  

This gives us the value that should come after softbank::

0x00007fffffffdc48│+0x0008: 0x0000000000333234 ("423"?)

Run it again with our new input:

gef➤  r
Starting program: /home/kali/Downloads/csaw/not_malware_patched 
What's your credit card number (for safekeeping) ?
>> softbank:423:BBBB

Continue past the first breakpoint and we get:

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x38              
$rbx   : 0x0               
$rcx   : 0x0               
$rdx   : 0x42              
$rsp   : 0x00007fffffffdc40  →  0x3136373831370000
$rbp   : 0x00007fffffffdcf0  →  0x00005555555558f0  →   endbr64 
$rsi   : 0x00005555555560c6  →  0x21736b6e61685400
$rdi   : 0x00007fffffffd9e0  →  0x00000000fbad8001
$rip   : 0x00005555555554d6  →   cmp dl, al
$r8    : 0x0               
$r9    : 0x00007fffffffdad0  →  0x0000000000000000
$r10   : 0x00007fffffffd980  →  "71876166"
$r11   : 0x0               
$r12   : 0x0000555555555130  →   endbr64 
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdc40│+0x0000: 0x3136373831370000   ← $rsp
0x00007fffffffdc48│+0x0008: 0x0000000000003636 ("66"?)
0x00007fffffffdc50│+0x0010: 0x0000000a42424242 ("BBBB\n"?)
0x00007fffffffdc58│+0x0018: 0x0000000000000000
0x00007fffffffdc60│+0x0020: 0x0000000040000000
0x00007fffffffdc68│+0x0028: 0x0000000000000000
0x00007fffffffdc70│+0x0030: "88557665573808687497"
0x00007fffffffdc78│+0x0038: "573808687497"
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x5555555554c8                  adc    edi, DWORD PTR [rsi-0x23]
   0x5555555554cb                  movzx  edx, BYTE PTR [rbp-0xa0]
   0x5555555554d2                  movzx  eax, BYTE PTR [rbp-0x80]
 → 0x5555555554d6                  cmp    dl, al
   0x5555555554d8                  je     0x5555555554e4
   0x5555555554da                  mov    edi, 0x1
   0x5555555554df                  call   0x555555555110 <exit@plt>
   0x5555555554e4                  movzx  edx, BYTE PTR [rbp-0x90]
   0x5555555554eb                  movzx  eax, BYTE PTR [rbp-0x70]
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "not_malware_pat", stopped 0x5555555554d6 in ?? (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555554d6 → cmp dl, al
[#1] 0x7ffff7ccccca → __libc_start_main(main=0x5555555552bc, argc=0x1, argv=0x7fffffffdde8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffddd8)
[#2] 0x55555555515e → hlt 
────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  

So now we know our input starts with:

softbank:423:

And it's now comparing 66 to BBBB\n

This string on the stack looks interesting, must be where the 66 comes from:

0x00007fffffffdc70│+0x0030: "88557665573808687497"

Right around the time I saw that 88557665573808687497 string above, datajerk chimed in with:

so angr solved it, but it does not work, but did help with the format
softbank:xxx:xxxxxxxxxxxxxxxxxxxx:end

xxxxxxxxxxxxxxxxxxxx looked like the right length for 88557665573808687497, so I gave it a shot:

kali@kali:~/Downloads/csaw$ echo 'softbank:423:88557665573808687497:end' | ./not_malware_patched 
What's your credit card number (for safekeeping) ?
>> Thanks!
Segmentation fault

Nice! That got us through all the remaining checks.

  if (local_47 != ':') {
    puts("Get out.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_c = 0;
  local_ac = 0x646e65;
  while( true ) {
    if (2 < local_c) {
      puts("Thanks!");
      FUN_00101229();
      return 0;
    }
    if (*(char *)((long)&local_ac + (long)local_c) != local_68[local_c + 0x22]) break;
    local_c = local_c + 1;
  }

The code above checks for :end and gives us the flag if we're successful:

void FUN_00101229(void)

{
  char local_118 [264];
  FILE *local_10;
  
  local_10 = fopen("flag.txt","r");
  __isoc99_fscanf(local_10,&DAT_00102061,local_118);
  puts(local_118);
  fclose(local_10);
  return;
}

My local run only segfaulted because I don't have a flag.txt file.

Solution

All that's left is to send our input to the remote server to get the flag:

kali@kali:~/Downloads/csaw$ echo 'softbank:423:88557665573808687497:end' | nc rev.chal.csaw.io 5008
What's your credit card number (for safekeeping) ?
>> Thanks!
flag{th4x_f0r_ur_cr3d1t_c4rd}

Addendum

I actually started down the gdb approach to understand the input format so that I could write a well-constrained angr script to solve the problem. Meanwhile, datajerk started with an angr script and found the format I needed to pair with the strings that I saw on the stack in gdb. Teamwork, FTW!

This is the approach he used to find the input format:

#!/usr/bin/env python3

# patch out all the init checks for angr
from pwn import *
binary = ELF('not_malware')
binary.write(0x18d1,5*b'\x90')
binary.write(0x12aa,5*b'\x90')
binary.write(0x12af,5*b'\x90')
binary.write(0x12b4,5*b'\x90')
binary.save('not_malware_patched')
os.chmod('not_malware_patched',0o755)

import angr, time, io
FIND_ADDR=0x401729 # puts("Thanks!");
t=time.time()
binary = open('./not_malware_patched','rb').read()
proj = angr.Project(io.BytesIO(binary),auto_load_libs=False)
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
simgr.use_technique(angr.exploration_techniques.DFS())
simgr.explore(find=FIND_ADDR)
print(simgr.found[0].posix.dumps(0))
print(time.time() - t,end="")
print(" seconds")

With no constraints, that gives us:

kali@kali:~/Downloads/csaw$ ./not_malware_angr.py
[*] '/home/kali/Downloads/csaw/not_malware'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
...
b'\x02\x02@\x10\x00@\x08\x04:\x00G\xf1:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:end \x10\x10@@\x08\x80@\x01 \x10\x00\x80@\x08\x02 @\x08\x00\x02\x00'
73.06609392166138 seconds

Let's add some constraints, given what we know about the format now.

#!/usr/bin/env python3

# patch out all the init checks for angr
from pwn import *
binary = ELF('not_malware')
binary.write(0x18d1,5*b'\x90')
binary.write(0x12aa,5*b'\x90')
binary.write(0x12af,5*b'\x90')
binary.write(0x12b4,5*b'\x90')
binary.save('not_malware_patched')
os.chmod('not_malware_patched',0o755)

import angr, time, io, claripy
FIND_ADDR=0x401729 # puts("Thanks!");
t=time.time()
binary = open('./not_malware_patched','rb').read()
proj = angr.Project(io.BytesIO(binary),auto_load_libs=False)

input_len = 37
ccard_chars = [claripy.BVS('ccard_%d' % i, 8) for i in range(input_len)]
ccard = claripy.Concat(*ccard_chars + [claripy.BVV(b'\n')])
state = proj.factory.entry_state(stdin=ccard)

for i, k in enumerate(ccard_chars):
    # only printable characters
    state.solver.add(k < 0x7f)
    state.solver.add(k > 0x20)
    if i <= 7:
        # first 8 chars are lowercase letters
        state.solver.add(k >= 0x61)
        state.solver.add(k <= 0x7a)
    elif i >= 9 and i <= 11:
        # then 3 digits
        state.solver.add(k >= 0x30)
        state.solver.add(k <= 0x39)
    elif i >= 13 and i <= 32:
        # then 20 digits
        state.solver.add(k >= 0x30)
        state.solver.add(k <= 0x39)
    elif i >= 34 and i <= 36:
        # then 3 lowercase letters
        state.solver.add(k >= 0x61)
        state.solver.add(k <= 0x7a)

simgr = proj.factory.simulation_manager(state)
simgr.use_technique(angr.exploration_techniques.DFS())
simgr.explore(find=FIND_ADDR)
print(simgr.found[0].posix.dumps(0))
print(time.time() - t,end="")
print(" seconds")

That gives us:

kali@kali:~/Downloads/csaw$ ./not_malware_angr.py 
...
b'ebdarcpd:002:00000000000000000000:end\n'
47.511109828948975 seconds

At least it's more legible, but still doesn't give us the right answer. And it varies from run to run.

kali@kali:~/Downloads/csa./not_malware_angr.py 
...
b'peepbank:006:00000000000000000000:end\n'
22.56076955795288 seconds

That must have something to do with the transformations on the stack. I think you actually have to run this thing to get the correct behavior, not just symbolic execution. I'll be interested to see if someone else comes up with a working angr solution for this challenge.

Even if it's possible to solve with angr, it still seems quicker to step through it with gdb in this case. By the time you've added enough constraints to angr, you probably know enough to solve it on your own.