deayzl's blog

[HITCON CTF 2024 Quals] antivirus 본문

CTF writeup/HITCON CTF

[HITCON CTF 2024 Quals] antivirus

deayzl 2024. 7. 22. 16:12

antivirus

It was a simple challenge using clamav which parses a binary in a specific rule.

 

https://github.com/Cisco-Talos/clamav

 

GitHub - Cisco-Talos/clamav: ClamAV - Documentation is here: https://docs.clamav.net

ClamAV - Documentation is here: https://docs.clamav.net - Cisco-Talos/clamav

github.com

 

run.sh:

#!/bin/sh

docker run -v /home/ctf/clamav/:/test/ --rm -it clamav/clamav clamscan --bytecode-unsigned -d/test/print_flag.cbc /test/sample.exe

 

print_flag.cbc:

ClamBCafhmlmengff|aa```c``a```|ah`cnbac`cecnb`c``biaabp`clamcoincidencejb:4096
PRINT_FLAG.{Fake,Real};Engine:56-255,Target:0;0;0:4d5a
Teddaaahdabahdacahdadahdaeahdafahdagahebheebgeebfeebaddc``daheb`eaacb`bbadb`baacb`bb`bb`baae`badahb`baaaae`badahb`daadb`aahdclhaahdb`bah
Eaeacabbbe|aebgefafdf``adbce|aecgefefkf``aebbe|amcgefdgfgifbgegcgnfafmfef``
G`am`@`bheBlbAeBbmBhoAdAcBdcAfBnoBknAfB`lB`lBioAbB`iB`lBioBjbBbmBhlBhoBbcB`iB`iBioB`iBhoBhoBioB`iBho`bgeBedBoeAfAgBdeBedAaAbBgfBegBmoBmoBnoBkoBhoBioBjoBfoBgoBdoBeoBcoB`oBaoBfgBlbAoBnbBdbBjbBibBjbBmjBebBebBfbBgbB`bBabBbbBcbBlaBmaBnaBicBiaBiaBjaBaaBdaBeaBfaBgaB`aBaaBbeBbaBldBmdBndBkdBhdBidBjdBodBddBedBfdBadB`dBadBbdBcdBlcBmcBncBicBhcBicBjcBkcBdcBecBfcBgjBacBacBbcBnmBlfBmfBnfBofBhfBifBjfBhfBdfAeBkmBgfB`fBagBbfBcfBleBmeBneBoeBhdBieBjeBkeBdeBeeBfeBgeB`eBadBbeBceBlhBmhBnhBohBhiBihBjhBkhBdhBehBfhBghB`hBahBbhBahBlgBmgBngBogBhgBigBjgBkgBdgBegBfgBgaBagBagBbgBoeBljBmjBnjBahBhjBijBjjBkjBdjBejBfjBiaBajBajBbjBekBmiBmiBniBaaBiiBiiBjiBmhBeiBeiBfiBkbBecBibBmcB`cBffBbgBigBnfBgfBggBcfBcgBobAdBlaAcBmfBnfBefBjfAjBfbBcaBeaBkbBfaBjaBfaAm@BdaBlcBnaAdAjBjaBkdBfdBndBndBkgBfeBodB`dBngB`gBffBffBneBhdBceBcnBddBomBiiBjkBljBjhBnjBokBliBdkBhkBckBlkBdkBbmBbdAoBieBmgBefBmgBofBhdBgfBkfBegBhfBhfBefB`dAbBhdBjkBnjBckBkkBgkBklBklBdmBbiBkiBhiBeoBcoBmaBaoBboBlbBmbBnbBobBhbBemBjbBkbBdbBebBfbBgbB`bBliBcmBhmBonBnnBmnAjBioBbnBanB`nBaeBdbBclBbeBiiB`eBgbBkeBmlBheBkcBlkBckBbkAbBjgBaoBikBbgBmdBlhAiBikBfhBdgBbkBklAjBmdBnlBbgBbkBabBkfBemBdmBklBigBokBfoBoiBhgBmmBliBciBbiBmjBoaBefBefBffBgfB`fBafBbfBcfBleBmeBneBgeBieBieBjeBmdBeeBeeBfeBbjB`eBaeBbeBceBlhBmhBnhBkgBhhBihBjhBkhBdhBehBfhBghB`hBahBbhBchBlgBmgBng`bfeB`eBbeBidBndBdeBoeBfdBldBadBgdBnbBbeBefBafBlf@`bfeB`eBbeBidBndBdeBoeBfdBldBadBgdBnbBfdBafBkfBef@`bad@Aa`bad@Aa`bad@Ab`bad@Ab`bad@Ac`bad@Ac`bad@Ad`bad@Ad`
A`b`bLbnab`dab`dabadab`eabad`b`b`aa`b`b`b`b`b`b`b`b`aa`b`d`b`d`b`d`b`b`bad`bad`ah`b`d`b`d`b`b`bad`bad`ah`aa`b`d`b`b`b`d`b`b`Fbhbag
Bbadaddbbaeac@db`baeabbad@dAbd``hbad@aC``ddaaafeab`baeClhadTaaafabaa
...

 

cbc to bytecode intructions:

clambc --printbcir print_flag.cbc > bcir.txt

 

bcir.txt:

...
found a total of 23 constants
CID  ID    VALUE
------------------------------------------------------------------------
  0 [ 30]: 0(0x0)
  1 [ 31]: 0(0x0)
  2 [ 32]: 2(0x2)
  3 [ 33]: 0(0x0)
  4 [ 34]: 1024(0x400)
  5 [ 35]: 396(0x18c)
  6 [ 36]: 15(0xf)
  7 [ 37]: 1(0x1)
  8 [ 38]: 0(0x0)
  9 [ 39]: 0(0x0)
 10 [ 40]: 396(0x18c)
 11 [ 41]: 396(0x18c)
 12 [ 42]: 0(0x0)
 13 [ 43]: 396(0x18c)
 14 [ 44]: 32(0x20)
 15 [ 45]: 32(0x20)
 16 [ 46]: 32(0x20)
 17 [ 47]: 32(0x20)
 18 [ 48]: 0(0x0)
 19 [ 49]: 1(0x1)
 20 [ 50]: 0(0x0)
 21 [ 51]: 15(0xf)
 22 [ 52]: 1(0x1)
------------------------------------------------------------------------
found a total of 53 total values
------------------------------------------------------------------------
FUNCTION ID: F.0 -> NUMINSTS 40
BB   IDX  OPCODE              [ID /IID/MOD]  INST
------------------------------------------------------------------------
  0    0  OP_BC_GEPZ          [36 /184/  4]  4 = gepz p.3 + (30)
  0    1  OP_BC_CALL_API      [33 /168/  3]  5 = seek[3] (31, 32)
  0    2  OP_BC_MEMSET        [40 /200/  0]  0 = memset (p.4, 33, 34)
  0    3  OP_BC_ICMP_EQ       [21 /108/  3]  6 = (5 == 35)
  0    4  OP_BC_BRANCH        [17 / 85/  0]  br 6 ? bb.2 : bb.1

  1    5  OP_BC_CALL_API      [33 /168/  3]  7 = setvirusname[4] (p.-2147483636, 36)
  1    6  OP_BC_COPY          [34 /174/  4]  cp 37 -> 0
  1    7  OP_BC_JMP           [18 / 90/  0]  jmp bb.6

  2    8  OP_BC_CALL_API      [33 /168/  3]  8 = seek[3] (38, 39)
  2    9  OP_BC_CALL_API      [33 /168/  3]  9 = read[1] (p.4, 40)
  2   10  OP_BC_CALL_DIRECT   [32 /163/  3]  10 = call F.1 (4, 41)
  2   11  OP_BC_COPY          [34 /174/  4]  cp 42 -> 1
  2   12  OP_BC_JMP           [18 / 90/  0]  jmp bb.4

  3   13  OP_BC_ICMP_ULT      [25 /129/  4]  11 = (26 < 43)
  3   14  OP_BC_COPY          [34 /174/  4]  cp 26 -> 1
  3   15  OP_BC_BRANCH        [17 / 85/  0]  br 11 ? bb.4 : bb.5

  4   16  OP_BC_COPY          [34 /174/  4]  cp 1 -> 12
  4   17  OP_BC_SHL           [8  / 44/  4]  13 = 12 << 44
  ...

 

 

I saw writeups from other ctfs that had any challenges related to clamav.

 

https://hxp.io/blog/94/SECCON-CTF-2022-Quals/

 

hxp | SECCON CTF 2022 Quals

Mon, 14 November 2022 ~ hxp SECCON CTF 2022 Quals This was a great CTF and we hacked some stuff. Here’s a relatively small subset of how. Looking forward to the finals! 🇯🇵 babybpf sisu | pwn, 10 solves, 278 points TLDR: found a working EBPF arb-rea

hxp.io

 

From above writeup, I saw the patch below:

-            DEFINE_ICMPOP(OP_BC_ICMP_EQ, res = (op0 == op1));
+            DEFINE_ICMPOP(OP_BC_ICMP_EQ, printf("%d: %x =?= %x\n", bb_inst, op0, op1);res = (op0 == op1));

 

So, I applied it to libclamav and built it.

diff --git a/libclamav/bytecode_vm.c b/libclamav/bytecode_vm.c
index 6c4d46c23..614cc400d 100644
--- a/libclamav/bytecode_vm.c
+++ b/libclamav/bytecode_vm.c
@@ -79,7 +79,7 @@ static inline int bcfail(const char *msg, long a, long b,
 #define CHECK_EQ(a, b)
 #define CHECK_GT(a, b)
 #endif
-#if 0 /* too verbose, use #ifdef CL_DEBUG if needed */
+#if 1 /* too verbose, use #ifdef CL_DEBUG if needed */
 #define CHECK_UNREACHABLE                                \
     do {                                                 \
         cli_dbgmsg("bytecode: unreachable executed!\n"); \
@@ -831,7 +831,8 @@ cl_error_t cli_vm_execute(const struct cli_bc *bc, struct cli_bc_ctx *ctx, const
             DEFINE_OP_BC_RET_VOID(OP_BC_RET_VOID * 5 + 3, uint8_t);
             DEFINE_OP_BC_RET_VOID(OP_BC_RET_VOID * 5 + 4, uint8_t);
 
-            DEFINE_ICMPOP(OP_BC_ICMP_EQ, res = (op0 == op1));
+            // DEFINE_ICMPOP(OP_BC_ICMP_EQ, res = (op0 == op1));
+            DEFINE_ICMPOP(OP_BC_ICMP_EQ, printf("%d: %x =?= %x\n", bb_inst, op0, op1);res = (op0 == op1));
             DEFINE_ICMPOP(OP_BC_ICMP_NE, res = (op0 != op1));
             DEFINE_ICMPOP(OP_BC_ICMP_UGT, res = (op0 > op1));
             DEFINE_ICMPOP(OP_BC_ICMP_UGE, res = (op0 >= op1));

 

The patch of #if 0 to #if 1 was meant to let the binary trace instructions dynamically using below TRACE_INST. (but actually not needed for the solve.py)

#define TRACE_INST(inst)                                                   \
            do {                                                                   \
                unsigned bbnum = 0;                                                \
                printf("LibClamAV debug: bytecode trace: executing instruction "); \
                cli_byteinst_describe(inst, &bbnum);                               \
                printf("\n");                                                      \
            } while (0)
        TRACE_INST(&inst);

 

I tried to use clambc binary to just run the bytecodes of print_flag.cbc but segfault occurred.

I thought that the reason of it was the variable map is actually 0.

So, I also applied the patch below.

diff --git a/clambc/bcrun.c b/clambc/bcrun.c
index efb167b06..bbe798c88 100644
--- a/clambc/bcrun.c
+++ b/clambc/bcrun.c
@@ -408,7 +408,9 @@ int main(int argc, char *argv[])
         // ctx was memset, so recursion_level starts at 0.
         cctx.recursion_stack[cctx.recursion_level].fmap = map;
         cctx.recursion_stack[cctx.recursion_level].type = CL_TYPE_ANY; /* ANY for the top level, because we don't yet know the type. */
-        cctx.recursion_stack[cctx.recursion_level].size = map->len;
+        // cctx.recursion_stack[cctx.recursion_level].size = map->len;
+        if (map == NULL)
+            cctx.recursion_stack[cctx.recursion_level].size = 0;
 
         cctx.fmap = cctx.recursion_stack[cctx.recursion_level].fmap;

 

./clamav/build/clambc/clambc --input ./example.exe ./print_flag.cbc

 

Now the debugging of the bytecodes is possible.

 

length must be 0x18c

The first condition shows the length of input binary check.

So the length of input binary must be 0x18c.

 

blocked at last condition

The last condition returned false.

I changed the input bytes to change the result of it.

Then I figured out that it just compares one byte with one byte of the input binary.

So the bruteforce of correct input binary is possible.

 

The script and patch below is what I made to bruteforce it at that time.

DEFINE_ICMPOP(OP_BC_ICMP_EQ, (bb_inst == 14) ? (printf("%d %d: %x =?= %x\n", count++, bb_inst, op0, op1)):(1);res = (op0 == op1););
import string
from pwn import *
import subprocess

def execute():
    subprocess.call('./clamav/build/clambc/clambc --input ./example.exe ./print_flag.cbc > res.txt', shell=True)

payload = list(0x41 for i in range(0x18c))
with open('./example.exe', 'wb') as f:
    f.write(bytes(payload))

count = 0
idx_list = list(range(0x18c))

while count < 0x18c:
    diff_idx = -1
    diff_idx = count
    assert diff_idx != -1

    for bf in range(0x100):
        payload[diff_idx] = bf
        with open('./example.exe', 'wb') as f:
            f.write(bytes(payload))
        execute()

        res = open('./res.txt', 'rt').read().strip().split('\n')
        for i in res:
            if f'{count} 14: ' in i:
                break
        res = i
        print(res)

        diff_i = -1
        print(diff_idx, bf, res, int(res[res.find(':')+1:res.find('=?=')], 16), int(res[res.find('=?=')+3:], 16))
        if int(res[res.find(':')+1:res.find('=?=')], 16) == int(res[res.find('=?=')+3:], 16):
            print('hit!')
            break
    print(bytes(payload))
    count += 1

 

 

result
flag

 

Thanks to skillful teammate, as soon as I let him know that the bruteforce is possible, he made a script to bruteforce it and solved it real quick.

This kind of thing happened a lot in other ctfs.

I think I gotta practice scripting faster.

 

'CTF writeup > HITCON CTF' 카테고리의 다른 글

[HITCON CTF 2023 Quals] LessEQualmore  (0) 2023.09.12
Comments