摘要
自修改代码Self-Modified Code(SMC)
是一类特殊的代码技术,即在运行时修改自身代码,从而使得程序实际行为与反汇编结果不符,同时修改前的代码段数据也可能非合法指令,从而无法被反汇编器识
解法:
根据静态分析结果直接修改程序二进制文件
在动态调试时将解密后的程序从内存中 dump 下来。
SMC出题&题解
明明还不懂就要出题了(
出题
这次出题有一个重大发现….原来smc是先生成未加密的二进制,然后另外用脚本去加密的。思路打开了
challenge.c
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h>
void stage2_end(void) __attribute__((optimize("O0"))); int encrypt_stage2(char *half_flag) __attribute__((noinline, optimize("O0")));
void stage1_end(void) __attribute__((optimize("O0"))); int encrypt_stage1(char *a1) __attribute__((noinline, optimize("O0")));
int encrypt_stage2(char *half_flag) { char target[10] = { (char)(~('v' ^ 0x42)), (char)(~('3' ^ 0x42)), (char)(~('r' ^ 0x42)), (char)(~('y' ^ 0x42)), (char)(~('_' ^ 0x42)), (char)(~('c' ^ 0x42)), (char)(~('0' ^ 0x42)), (char)(~('0' ^ 0x42)), (char)(~('l' ^ 0x42)), (char)(~('}' ^ 0x42))}; int i;
for (i = 0; i < 10; ++i) { if ((char)(~half_flag[i] ^ 0x42) != target[i]) return 0; } return 1; } void stage2_end(void) { asm("nop"); }
int encrypt_stage1(char *a1) { char expected[17] = "flag{SMC_n3st3d_"; int i, j; int pagesize; void *aligned_addr; unsigned int stage2_size;
for (i = 0; i < 16; ++i) { if (a1[i] != expected[i]) return 0; }
pagesize = sysconf(_SC_PAGESIZE); aligned_addr = (void *)((unsigned int)encrypt_stage2 & ~(pagesize - 1));
mprotect(aligned_addr, pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC);
stage2_size = (unsigned int)stage2_end - (unsigned int)encrypt_stage2;
for (j = 0; j < stage2_size; ++j) { *((unsigned char *)encrypt_stage2 + j) ^= a1[j % 4]; }
return encrypt_stage2(a1 + 16); } void stage1_end(void) { asm("nop"); }
int main(int argc, char **argv) { char Str[60]; size_t v4; int i; int pagesize; void *aligned_addr; unsigned int stage1_size;
puts("Please input your flag:"); if (scanf("%50s", Str) != 1) exit(1);
v4 = strlen(Str); if (v4 != 26) { puts("Wrong length!"); exit(0); }
pagesize = sysconf(_SC_PAGESIZE); aligned_addr = (void *)((unsigned int)encrypt_stage1 & ~(pagesize - 1));
if (mprotect(aligned_addr, pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { perror("mprotect failed"); exit(1); }
stage1_size = (unsigned int)stage1_end - (unsigned int)encrypt_stage1;
for (i = 0; i < stage1_size; ++i) { *((unsigned char *)encrypt_stage1 + i) ^= 0x33; }
if (encrypt_stage1(Str)) puts("Congratulations! You got the real flag!"); else puts("Wrong! Try again.");
return 0; }
|
build.py
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 32 33 34 35 36 37 38 39 40 41
| from pwn import *
elf = ELF('./challenge_raw')
stage1_addr = elf.symbols['encrypt_stage1'] stage1_end = elf.symbols['stage1_end'] stage1_size = stage1_end - stage1_addr
stage2_addr = elf.symbols['encrypt_stage2'] stage2_end = elf.symbols['stage2_end'] stage2_size = stage2_end - stage2_addr
print(f"[*] Stage 1 Size: {stage1_size} bytes") print(f"[*] Stage 2 Size: {stage2_size} bytes")
stage1_code = bytearray(elf.read(stage1_addr, stage1_size)) stage2_code = bytearray(elf.read(stage2_addr, stage2_size))
key = b"flag" for i in range(stage2_size): stage2_code[i] ^= key[i % 4]
elf.write(stage2_addr, bytes(stage2_code))
for i in range(stage1_size): stage1_code[i] ^= 0x33
elf.write(stage1_addr, bytes(stage1_code))
elf.save('./smc_revenge') os.chmod('./smc_revenge', 0o755) print("[+] SMC Challenge successfully built: ./smc_revenge")
|
编译
1 2 3 4 5 6
| //32位的,可能需要 sudo apt install gcc-multilib //编译和加密 gcc -m32 -O0 challenge.c -o challenge_raw python3 build.py strip ./smc_revenge
|
题解
SMC解题的两种方法。如果加密比较复杂,就不如直接使用gdb查看解密后的代码了。不过这种可能就会加上点反调试手段
这就是解题的博弈吗
IDA脚本解密
main里能看到stage1的加密逻辑。异或0x33
1 2
| for ( i = 0; (char *)stage1_end - (char *)encrypt_stage1 > i; ++i ) *((_BYTE *)encrypt_stage1 + i) ^= 0x33u;
|
解密stage1的ida python脚本:
1 2 3 4 5 6 7
| start = 0x000012A4 end = 0x000013DD
for i in range(start, end): patch_byte(i, get_wide_byte(i) ^ 0x33)
print("Decryption done!")
|
得到stage1,得到半段flag:flag{SMC_n3st3d_,以及stage2的加密逻辑,以flag为key进行异或
解密stage2
1 2 3 4 5 6 7 8 9 10 11
| start = 0x0000120D end = 0x00001293
key = "flag" for i in range(end - start): addr = start + i byte = get_wide_byte(addr) patch_byte(addr, byte ^ ord(key[i % 4]))
print("Stage 2 Decrypted!")
|
gdb
在call stage1那一行下断点。因为开了PIE所以要加偏移
1 2
| pwndbg> breakrva 0x152C Breakpoint 2 at 0x5655652c
|
因为有判断长度的逻辑,运行后输入一个长度正确的字符串,不然不会跑到解密去。到断点之后查看指令是:
1
| 0x5655652c call 0x565562a4 <0x565562a4>
|
0x565562a4就是stage1的地址了。用x/i指令查看stage1的汇编,可以直接分析也可以patch到IDA里反编译。得到半段flag:flag{SMC_n3st3d_,并且发现需要以输入的前四位为key解密stage2,所以要求输入一个flag开头的长度正确的值,再次运行。
后面就不说了,就是停在stage2再查看汇编
[网鼎杯 2020 青龙组]jocker
题目链接:https://buuoj.cn/challenges#[%E7%BD%91%E9%BC%8E%E6%9D%AF%202020%20%E9%9D%92%E9%BE%99%E7%BB%84]jocker