Tags: re
Rating: 4.0
# 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:
```c
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`:
```c
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:
```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);
}
```
`FUN_001012a6` is basically an initialization function of sorts:
```c
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:
```c
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:
```c
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:
```c
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.
```python
#!/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:
```c
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 <[email protected]>
0x555555555046 <strncmp@plt+6> push 0x1
0x55555555504b <strncmp@plt+11> jmp 0x555555555020
0x555555555050 <__isoc99_fscanf@plt+0> jmp QWORD PTR [rip+0x2fd2] # 0x555555558028 <[email protected]>
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:
```c
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:
```c
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:
```c
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](https://github.com/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.
```c
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:
```c
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](https://github.com/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:
```python
#!/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.
```python
#!/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.
It is possible to solve with angr without constraints. We just did it like this: https://gist.github.com/Es7evam/8d8eecf235cb8d6b2edc2752ad7a35ba
It prints several outputs, then you can test it easily with a file for example. In our case the second try was already a valid input.