일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Buffer Overflow
- python
- h4cking game
- 웹해킹
- 워게임
- TeamH4C
- 2022 Fall GoN Open Qual CTF
- CTF
- deayzl
- writeup
- KAIST
- hack
- System Hacking
- webhacking
- hacking game
- ctf player
- dreamhack
- WEB
- webhacking.kr
- cryptography
- Gon
- hacking
- got overwrite
- christmas ctf
- Wargame
- pwnable
- Wreckctf
- crypto
- reversing
- 해킹
- Today
- Total
deayzl's blog
[HITCON CTF 2024 Quals] antivirus 본문
It was a simple challenge using clamav which parses a binary in a specific rule.
https://github.com/Cisco-Talos/clamav
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/
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.
The first condition shows the length of input binary check.
So the length of input binary must be 0x18c.
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
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 |
---|