- Source: zer0ptz CTF 2023
- Author: ptr-yudai
バイナリファイルが与えられる。逆アセンブル結果はこれ。
void main(void)
{
int iVar1;
undefined4 extraout_var;
long lVar2;
undefined8 *puVar3;
undefined8 local_208 [16];
undefined local_188 [384];
puVar3 = local_208;
for (lVar2 = 0x200; lVar2 != 0; lVar2 = lVar2 + -1) {
*(undefined *)puVar3 = 0;
puVar3 = (undefined8 *)((long)puVar3 + 1);
}
local_208[0] = 0x80;
write(1,"FLAG: ",0xe);
read(0,local_208,0x80);
RC4_setkey(&key,sbox);
RC4_encrypt(local_208,local_188,sbox);
iVar1 = memcmp(local_188,&enc,0x80);
if (CONCAT44(extraout_var,iVar1) == 0) {
puts("Correct!");
}
else {
puts("Wrong...");
}
return;
}
ユーザー入力をRC4で暗号化し、enc
と照合して結果を返す。よってenc
をkey
で復号化すればflagが得られそうに見えるが、そうはいかない。
処理を追っていく。memcmp
が呼び出し規約の異なる独自実装となっていて、関数内部では引数__s1
の代わりにR14
を使用している。よく見ると__s2
も引数ではなく変数dat
を参照している。
int __stdcall memcmp (void * __s1 , void * __s2 , size_t _
int EAX:4 <RETURN>
void * RDI:8 __s1
void * RSI:8 __s2
size_t RDX:8 __n
memcmp XREF[1]: main:001012bd (c)
0010115b 31 c0 XOR EAX ,EAX
0010115d 31 c9 XOR ECX ,ECX
0010115f 48 8d 35 LEA __s2 ,[dat ] = 4c0733dc85c4cf78
1a 2e 00 00
lx XREF[1]: 00101173 (j)
00101166 41 8a 1c 0e MOV BL,byte ptr [R14 + RCX *0x1 ]
0010116a 32 1c 0e XOR BL,byte ptr [__s2 + RCX *0x1 ]=>dat = 4c0733dc85c4cf78
0010116d 08 d8 OR AL,BL
0010116f ff c1 INC ECX
00101171 39 d1 CMP ECX ,__n
00101173 72 f1 JC lx
00101175 c3 RET
ではR14
はどこから来たのか。RC4_setkey
とRC4_encrypt
を見てみる。
undefined RC4_setkey ()
undefined AL:1 <RETURN>
RC4_setkey XREF[1]: main:0010128d (c)
00101176 31 c9 XOR ECX ,ECX
00101178 31 d2 XOR EDX ,EDX
.loop_init_sbox XREF[1]: 00101187 (j)
0010117a 41 88 4c MOV byte ptr [R13 + RCX *0x1 ],CL
0d 00
0010117f ff c1 INC ECX
00101181 81 f9 00 CMP ECX ,0x100
01 00 00
00101187 72 f1 JC .loop_init_sbox
00101189 31 c9 XOR ECX ,ECX
0010118b 31 db XOR EBX ,EBX
.loop_key_schedule XREF[1]: 001011c4 (j)
0010118d 41 0f b6 MOVZX EAX ,byte ptr [R13 + RCX *0x1 ]
44 0d 00
00101193 01 c2 ADD EDX ,EAX
00101195 41 0f b6 MOVZX EAX ,byte ptr [R12 + RBX *0x1 ]
04 1c
0010119a 01 c2 ADD EDX ,EAX
0010119c 0f b6 d2 MOVZX EDX ,DL
0010119f 41 8a 7c MOV DIL ,byte ptr [R13 + RDX *0x1 ]
15 00
001011a4 41 8a 74 MOV SIL ,byte ptr [R13 + RCX *0x1 ]
0d 00
001011a9 41 88 74 MOV byte ptr [R13 + RDX *0x1 ],SIL
15 00
001011ae 41 88 7c MOV byte ptr [R13 + RCX *0x1 ],DIL
0d 00
001011b3 ff c3 INC EBX
001011b5 83 fb 08 CMP EBX ,0x8
001011b8 72 02 JC .skip_mod_keylen
001011ba 31 db XOR EBX ,EBX
.skip_mod_keylen XREF[1]: 001011b8 (j)
001011bc ff c1 INC ECX
001011be 81 f9 00 CMP ECX ,0x100
01 00 00
001011c4 72 c7 JC .loop_key_schedule
001011c6 c3 RET
undefined RC4_encrypt ()
undefined AL:1 <RETURN>
RC4_encrypt XREF[1]: main:001012a4 (c)
001011c7 31 c9 XOR ECX ,ECX
001011c9 31 d2 XOR EDX ,EDX
001011cb 31 db XOR EBX ,EBX
.loop_encrypt XREF[1]: 00101218 (j)
001011cd ff c2 INC EDX
001011cf 0f b6 d2 MOVZX EDX ,DL
001011d2 41 0f b6 MOVZX EAX ,byte ptr [R13 + RDX *0x1 ]
44 15 00
001011d8 01 c3 ADD EBX ,EAX
001011da 0f b6 db MOVZX EBX ,BL
001011dd 41 8a 7c MOV DIL ,byte ptr [R13 + RDX *0x1 ]
15 00
001011e2 41 8a 74 MOV SIL ,byte ptr [R13 + RBX *0x1 ]
1d 00
001011e7 41 88 74 MOV byte ptr [R13 + RDX *0x1 ],SIL
15 00
001011ec 41 88 7c MOV byte ptr [R13 + RBX *0x1 ],DIL
1d 00
001011f1 41 0f b6 MOVZX EAX ,byte ptr [R13 + RDX *0x1 ]
44 15 00
001011f7 41 0f b6 MOVZX EDI ,byte ptr [R13 + RBX *0x1 ]
7c 1d 00
001011fd 01 f8 ADD EAX ,EDI
00101202 41 0f b6 MOVZX EAX ,byte ptr [R13 + RAX *0x1 ]
44 05 00
00101208 41 32 04 0f XOR AL,byte ptr [R15 + RCX *0x1 ]
0010120c 41 88 04 0e MOV byte ptr [R14 + RCX *0x1 ],AL
00101210 ff c1 INC ECX
00101212 81 f9 80 CMP ECX ,0x80
00 00 00
00101218 72 b3 JC .loop_encrypt
0010121a c3 RET
これらの関数も呼び出し規約が異なる。おそらくR12
が鍵、R13
がS-Box、R14
が暗号化後の値、R15
がユーザー入力に対応している。
main関数を見ているとR12
に変数val
をセットしている処理があった。
0010123b 4c 8d 25 LEA R12 ,[val ] = 1145141919810931
36 2d 00 00
よって鍵がval
、暗号化後の比較対象がdat
だと分かるので、これらをバイナリの中から探して復号する。
solverを書く。RC4は暗号化と復号化の処理が同じになる。
val = bytes([
0x31, 0x09, 0x81, 0x19, 0x19, 0x14, 0x45, 0x11
])
dat = bytes([
0x78, 0xcf, 0xc4, 0x85, 0xdc, 0x33, 0x07, 0x4c,
0x93, 0x35, 0xfb, 0x7c, 0x10, 0x8e, 0xbe, 0x93,
0x28, 0xe6, 0x2e, 0x75, 0xda, 0x5e, 0x85, 0xc5,
0x91, 0x15, 0x75, 0x89, 0x48, 0x0e, 0x29, 0xa4,
0xf9, 0xa6, 0x3a, 0x6e, 0x1f, 0x84, 0xf7, 0x42,
0xb0, 0x93, 0x31, 0xf0, 0x68, 0xc0, 0x43, 0x38,
0x07, 0x32, 0x09, 0x57, 0xda, 0x32, 0x44, 0xcf,
0xcd, 0x8f, 0xe5, 0xbf, 0xe3, 0xd6, 0xbb, 0x59,
0x9a, 0x6a, 0x84, 0x85, 0xd3, 0x22, 0xa9, 0x8e,
0xb5, 0xea, 0xbd, 0x57, 0xde, 0xb1, 0x6c, 0x93,
0xe4, 0x74, 0x70, 0xac, 0x1a, 0x03, 0xd9, 0x16,
0x9f, 0xbc, 0x97, 0xfb, 0x85, 0xd9, 0xa6, 0x9e,
0xd4, 0xd6, 0x02, 0x59, 0xd5, 0x28, 0xb3, 0x93,
0x16, 0xb6, 0xc4, 0x78, 0xc4, 0xa2, 0x12, 0xd2,
0xef, 0xb1, 0x54, 0x18, 0xfd, 0x76, 0x51, 0xa3,
0x5e, 0x57, 0xb8, 0x58, 0x4b, 0x1e, 0xe2, 0x41
])
# RC4 encrypt by copilot :)
def rc4(key, data):
S = list(range(256))
j = 0
out = bytearray()
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
for b in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(b ^ S[(S[i] + S[j]) % 256])
return bytes(out)
flag = rc4(val, dat)
print(flag)
flagが得られた。
zer0pts{d0n'7_4lw4y5_7ru57_d3c0mp1l3r}