RE新瓶装旧酒之printf虚拟机

众所周知,printf中可以自定义格式占位符(spec)的行为,就像使用%x可以输出一个数的十六进制一样,在使用register_printf_function后可以将指定spec与指定转换函数绑定,从而自定义输出行为。
虽然这种方式已经弃用,但最近似乎在新的领域焕发了第二春——用它的自定义行为来运行一个CTF Reverse里常见的VM。单是2023年下半年居然有两场比赛碰到了printf虚拟机的题目,而再往前追溯或许只有2021年GoogleCTF那道经典的Weather了(https://ctftime.org/task/16546)。
本文将介绍printf虚拟机的实现原理,讲解该类逆向题目的解题方法,并以BRICS+CTF的shellcode???和楚慧杯的scalar两道今年做到的赛题为例将解题方法运用到实战中。

自定义printf

有关自定义printf的操作细节,我们可以在GNU的文档里看到:
https://www.gnu.org/software/libc/manual/html_node/Customizing-Printf.html
这里仅挑出一些与赛题相关的主要知识点进行介绍
int register_printf_function (int spec, printf_function handler-function, printf_arginfo_function arginfo-function)
register_printf_function函数用于注册新的输出转换,需要在printf虚拟机前执行,一般为了掩人耳目会放在init中作为初始函数表的一部分。其参数含义如下:
  • spec:格式占位符。

    spec'A'时,该函数注册的是%A的输出转换。可以覆盖如%x、%s等默认spec的定义,建议一般使用大写字母以示区分。

  • handler-function:处理该spec的转换函数。

    int function (FILE *stream, const struct printf_info *info, const void *const *args)

    简单来说,info是在解析到spec时获取的信息结构体,如解析到%20A时,info -> width = 20(结构体的详细介绍见后文);args是获取的参数列表,通过上文的arginfo-function指定参数个数n以后,它会在printf的参数列表中依次获取n个参数。

  • arginfo-function:指定该spec所需的参数个数和参数类型。

    size_t parse_printf_format (const char *templatesize_t n, int *argtypes)

    这里需要关注的是argtypes和返回值,argtypes是一个数组,分别指定了各参数的类型,如PA_INT、PA_CHAR等,最后返回该spec所需的参数个数。


解题方法

既然如此,我们的整体思路就是:
  1. 找到register_printf_function
  2. 记录各格式占位符的作用
  3. 编写VM解释器将format字符串转换为汇编
  4. 阅读汇编代码求解flag
除了第一步以外,剩下的步骤都与VM解题方法大致相同,printf虚拟机的作用是将原本VM的switch-case分派复杂化,并且在不起眼的printf中完成整个运行。

BRICS+CTF | shellcode???

如果没做过printf虚拟机的话,这道题上来看到main函数里只有一句printf,大概会觉得很莫名其妙:


RE新瓶装旧酒之printf虚拟机

找到.init_array,分别查看这里的各个函数,就能发现第二个函数就是我们所要找的注册printf转换函数的地方:

RE新瓶装旧酒之printf虚拟机

RE新瓶装旧酒之printf虚拟机

main函数里面的printf用了%f,所以看'f'的handler:
undefined8 FUN_00401fe0(void) {
  uint arg;
  char *format;
  int i;
  
  for (i = 0; i < 0x34; i = i + 1) {
    format = (char *)malloc(0x100);
    arg = 0;
    __isoc99_scanf("%s %d",format,&arg);
    printf(format,(ulong)arg);
    free(format);
  }
  printf("%w%V%b%s%V%m%J%s%R%H",0x1223f37,0x2af985a,0x2ba4c9,0x693d920,0x6f794f,0x62,0xfff1d63c,
         0x623c513,0x2584,0x474f4b2,0x27d8f97);
  printf("%w%V%b%s%V%m%J%s%R%H",0x3fe9fae,0x5d0a399,0x3c7c1e5,0x426e19e,0x3ac53d7,0x69,0xffee7c50,
         0x9abb47,0x2b11,0x25242a2,0x2f511fb);
  printf("%w%V%b%s%V%m%J%s%R%H",0x79bc35,0x1b7bd90,0x4383c74,0x580ac2a,0x73751d6,0x73,0xffe8d24e,
         0x45161b0,0x33a9,0x2f87765,0x10ad040);
  printf("%w%V%b%s%V%m%J%s%R%H",0xa6d19b,0x1a6a0d3,0x23784ab,0x170fb47,0x6279f4e,0x7b,0xffe3b18d,
         0x18d51b8,0x3b19,0x22e1923,0x44c107e);
  printf("%w%V%b%s%V%m%J%s%R%H",0x61d2d1a,0x2fd3ab5,0x3674d99,0x2ef1ece,0x4320e5,0x30,0xfffe7d90,
         0x72efd70,0x900,0x5054985,0x8a2648);
  printf("%w%V%b%s%V%m%J%s%R%H",0x73273e1,0x1ea1176,0x22750f6,0x43b6a7a,0x6fc9446,0x5f,0xfff32412,
         0x36aa1c2,0x2341,0x2bdfa2d,0x19df6c1);
  printf("%w%V%b%s%V%m%J%s%R%H",0x709b202,0x10c7776,0x1aca7bf,0x2846728,0x2acdfd,0x30,0xfffe6c39,
         0x50aceaf,0x900,0x3fd6e93,0x6f1ae51);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2839ef3,0x14e6f45,0x3ac805f,0x3df8740,0x9ecc96,0x5f,0xfff3112a,
         0x878285,0x2341,0x1465a45,0x61cb62);
  printf("%w%V%b%s%V%m%J%s%R%H",0x48975af,0x3e4659f,0x2e62dba,0x43051c2,0x70c2c14,0x61,0xfff24223,
         0x726a781,0x24c1,0x3c59ed9,0x7013f38);
  printf("%w%V%b%s%V%m%J%s%R%H",0x5af8e8f,0x14a0790,0x11a0297,0x1639c3,0x2f4684f,0x5f,0xfff31a25,
         0x69dcc81,0x2341,0x3d1ec81,0x4b5c9aa);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2384a5d,0x6023773,0x751b5a4,0x42906ae,0x2abc591,0x30,0xfffe8490,
         0x1ba4382,0x900,0x4c841a0,0x5d6c676);
  printf("%w%V%b%s%V%m%J%s%R%H",0x10f72b,0x4555f11,0x73006b8,0x4bd032f,0x21f61cb,0x5f,0xfff316c5,
         0x130456c,0x2341,0x66232ed,0x24880b7);
  printf("%w%V%b%s%V%m%J%s%R%H",0x61629be,0x142e4d1,0x6ef88f9,0x124e9d7,0x75323be,0x75,0xffe7c35c,
         0x13e54b,0x3579,0x2055d85,0x40a037e);
  printf("%w%V%b%s%V%m%J%s%R%H",0x1a974cb,0x4c13f72,0x3f91ead,0xc0b580,0x2ceb6c6,0x31,0xfffe57b0,
         0x73751fd,0x961,0x2448dd2,0x35e7795);
  printf("%w%V%b%s%V%m%J%s%R%H",0xd61454,0x5ea2480,0x638cf01,0xc1897,0x59b1f2b,0x70,0xffeaa081,
         0x12d8934,0x3100,0x2507798,0x58616c);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2b7cfa7,0x27e600c,0x2aac5b2,0x58544a3,0x2b98aa3,0x72,0xffe99861,
         0x60f21dd,0x32c4,0x7021d82,0x1ee19b5);
  printf("%w%V%b%s%V%m%J%s%R%H",0x35491c5,0x2341b2f,0x21a5253,0x3d9b0ad,0x23c1e66,0x33,0xfffe1d16,
         0x3e89c02,0xa29,0x5c82116,0x4129fef);
  printf("%w%V%b%s%V%m%J%s%R%H",0x4e0a10d,0x71efc70,0x6092171,0x869088,0x63dba22,0x74,0xffe85900,
         0x570839b,0x3490,0x1a2b21d,0x2e03a43);
  printf("%w%V%b%s%V%m%J%s%R%H",0x296ee85,0x63d72c7,0x6d9cd72,0x70f9b7d,0x4dbbe1a,0x31,0xfffe6818,
         0x48cad9,0x961,0x581996e,0x58772);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2fb0010,0x5c0791d,0x145d223,0xac2f74,0x5368a28,0x5f,0xfff31065,
         0x55bca2a,0x2341,0x4b1ebf8,0x6a49a88);
  printf("%w%V%b%s%V%m%J%s%R%H",0x70eecbd,0x36ecff2,0x4924a1d,0x1f74f07,0x11d896d,0x75,0xffe7ab43,
         0x23f3af7,0x3579,0x72f2132,0x25a98d9);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2f1cb4,0x3f205f7,0x75aa10f,0x3e936c7,0x5188cee,0x5f,0xfff2fe92,
         0x708fd26,0x2341,0x2749ae8,0x6257695);
  printf("%w%V%b%s%V%m%J%s%R%H",0x4c7a950,0x5ec022e,0x4b9a7a4,0x2e278f4,0x6200e00,0x30,0xfffe8021,
         0x63b7839,0x900,0x42dc8d5,0x13d670a);
  printf("%w%V%b%s%V%m%J%s%R%H",0x47b6288,0x4f8f54e,0x142674c,0x8c3951,0x7037c99,100,0xfff0e101,
         0x3ecce47,10000,0x66fbc4d,0x37bed26);
  printf("%w%V%b%s%V%m%J%s%R%H",0x642ad16,0x740e6c5,0x258eae7,0x65e3017,0x586efb0,0x6a,0xffee03b9,
         0x4afb046,0x2be4,0x4c31ce0,0x52abb1f);
  printf("%w%V%b%s%V%m%J%s%R%H",0x55db7f6,0x6aef138,0x42210e5,0x72fd711,0xd8d75e,0x62,0xfff1e081,
         0x1ca0e4d,0x2584,0x325351f,0x69c0c21);
  putchar(0x59);
  putchar(0x65);
  putchar(0x73);
  return 0;
}
这里主要用到的指令序列都是"%w%V%b%s%V%m%J%s%R%H",而前面的for循环让我们输入字符串format作为指令和一个数字arg作为参数,姑且相当于是平时题目中的输入。
由于主指令序列很短,用出VM解释器倒有点杀鸡焉用牛刀的感觉,干脆直接手逆出汇编。如%w的handler:
undefined8 FUN_00400ed4(void) {
  long v1;
  double v0;
  
  ptr = ptr + -1;
  v0 = pow((double)arr[ptr],2.0);
  v1 = (long)ptr;
  ptr = ptr + 1;
  arr[v1] = (int)v0;
  return 0;
}
实际上实现的是:
arr[ptr-1] = arr[ptr-1] ** 2
表示成汇编(随便抓一个寄存器rsi用来表示ptr):
POW [rsi-1], 2
那么主指令序列全部逆出来就是:
;%w
;POW [rsi-1], 2

POP rax
POW rax, 2
PUSH rax
;%V
;mov [rsi], [rsi-2]
;inc rsi

PUSH [rsp-2]
;%b
;POW [rsi-1], 3

POP rax
POW rax, 3
PUSH rax
;%s
;sub [rsi-2], [rsi-1]
;dec rsi

POP rax
POP rbx
sub rbx, rax
PUSH rbx
;%V
;mov [rsi], [rsi-2]
;inc rsi

PUSH [rsp-2]
;%m
;mul [rsi-1], arg5

POP rax
MUL rax, arg5
PUSH rax
;%J
;sub arg6, [rsi-1]
;mov [rsi-1], arg6

POP rax
MOV rbx, arg6
SUB rbx, rax
PUSH rbx
;%s
;sub [rsi-2], [rsi-1]
;dec rsi

POP rax
POP rbx
sub rbx, rax
PUSH rbx
;%R
;cmp [rsi-1], arg8
;jnz "No"
;dec rsi

POP rax
cmp rax, arg8
;%H
;dec rsi

POP
从注册printf转换函数的函数中可以看到,所有的arginfo都return了1,代表都需要一个参数,而有些handler里却没用到arg,这算是一个小坑:

RE新瓶装旧酒之printf虚拟机

所以上文汇编中的arg5是printf调用的参数列表中的第六个参数(从0标起),以此类推
再写成Python是:
def f(v5, v6, v8):
    assert v8 == (arr[ptr-1] ** 2) - (arr[ptr-2] ** 3) - (v6 - (arr[ptr-2] * v5))
    ptr -= 2
v5、v6和v8这三个参数可以从程序的汇编代码中提取(伪代码的参数没有展示完全),可以用逐两字节爆破的方式爆破出arr[ptr-1]arr[ptr-2],从而推出整个flag。
s = '''
  printf("%w%V%b%s%V%m%J%s%R%H",0x1223f37,0x2af985a,0x2ba4c9,0x693d920,0x6f794f,0x62,0xfff1d63c,0x623c513,0x2584,0x474f4b2,0x27d8f97);
  printf("%w%V%b%s%V%m%J%s%R%H",0x3fe9fae,0x5d0a399,0x3c7c1e5,0x426e19e,0x3ac53d7,0x69,0xffee7c50,0x9abb47,0x2b11,0x25242a2,0x2f511fb);
  printf("%w%V%b%s%V%m%J%s%R%H",0x79bc35,0x1b7bd90,0x4383c74,0x580ac2a,0x73751d6,0x73,0xffe8d24e,0x45161b0,0x33a9,0x2f87765,0x10ad040);
  printf("%w%V%b%s%V%m%J%s%R%H",0xa6d19b,0x1a6a0d3,0x23784ab,0x170fb47,0x6279f4e,0x7b,0xffe3b18d,0x18d51b8,0x3b19,0x22e1923,0x44c107e);
  printf("%w%V%b%s%V%m%J%s%R%H",0x61d2d1a,0x2fd3ab5,0x3674d99,0x2ef1ece,0x4320e5,0x30,0xfffe7d90,0x72efd70,0x900,0x5054985,0x8a2648);
  printf("%w%V%b%s%V%m%J%s%R%H",0x73273e1,0x1ea1176,0x22750f6,0x43b6a7a,0x6fc9446,0x5f,0xfff32412,0x36aa1c2,0x2341,0x2bdfa2d,0x19df6c1);
  printf("%w%V%b%s%V%m%J%s%R%H",0x709b202,0x10c7776,0x1aca7bf,0x2846728,0x2acdfd,0x30,0xfffe6c39,0x50aceaf,0x900,0x3fd6e93,0x6f1ae51);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2839ef3,0x14e6f45,0x3ac805f,0x3df8740,0x9ecc96,0x5f,0xfff3112a,0x878285,0x2341,0x1465a45,0x61cb62);
  printf("%w%V%b%s%V%m%J%s%R%H",0x48975af,0x3e4659f,0x2e62dba,0x43051c2,0x70c2c14,0x61,0xfff24223,0x726a781,0x24c1,0x3c59ed9,0x7013f38);
  printf("%w%V%b%s%V%m%J%s%R%H",0x5af8e8f,0x14a0790,0x11a0297,0x1639c3,0x2f4684f,0x5f,0xfff31a25,0x69dcc81,0x2341,0x3d1ec81,0x4b5c9aa);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2384a5d,0x6023773,0x751b5a4,0x42906ae,0x2abc591,0x30,0xfffe8490,0x1ba4382,0x900,0x4c841a0,0x5d6c676);
  printf("%w%V%b%s%V%m%J%s%R%H",0x10f72b,0x4555f11,0x73006b8,0x4bd032f,0x21f61cb,0x5f,0xfff316c5,0x130456c,0x2341,0x66232ed,0x24880b7);
  printf("%w%V%b%s%V%m%J%s%R%H",0x61629be,0x142e4d1,0x6ef88f9,0x124e9d7,0x75323be,0x75,0xffe7c35c,0x13e54b,0x3579,0x2055d85,0x40a037e);
  printf("%w%V%b%s%V%m%J%s%R%H",0x1a974cb,0x4c13f72,0x3f91ead,0xc0b580,0x2ceb6c6,0x31,0xfffe57b0,0x73751fd,0x961,0x2448dd2,0x35e7795);
  printf("%w%V%b%s%V%m%J%s%R%H",0xd61454,0x5ea2480,0x638cf01,0xc1897,0x59b1f2b,0x70,0xffeaa081,0x12d8934,0x3100,0x2507798,0x58616c);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2b7cfa7,0x27e600c,0x2aac5b2,0x58544a3,0x2b98aa3,0x72,0xffe99861,0x60f21dd,0x32c4,0x7021d82,0x1ee19b5);
  printf("%w%V%b%s%V%m%J%s%R%H",0x35491c5,0x2341b2f,0x21a5253,0x3d9b0ad,0x23c1e66,0x33,0xfffe1d16,0x3e89c02,0xa29,0x5c82116,0x4129fef);
  printf("%w%V%b%s%V%m%J%s%R%H",0x4e0a10d,0x71efc70,0x6092171,0x869088,0x63dba22,0x74,0xffe85900,0x570839b,0x3490,0x1a2b21d,0x2e03a43);
  printf("%w%V%b%s%V%m%J%s%R%H",0x296ee85,0x63d72c7,0x6d9cd72,0x70f9b7d,0x4dbbe1a,0x31,0xfffe6818,0x48cad9,0x961,0x581996e,0x58772);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2fb0010,0x5c0791d,0x145d223,0xac2f74,0x5368a28,0x5f,0xfff31065,0x55bca2a,0x2341,0x4b1ebf8,0x6a49a88);
  printf("%w%V%b%s%V%m%J%s%R%H",0x70eecbd,0x36ecff2,0x4924a1d,0x1f74f07,0x11d896d,0x75,0xffe7ab43,0x23f3af7,0x3579,0x72f2132,0x25a98d9);
  printf("%w%V%b%s%V%m%J%s%R%H",0x2f1cb4,0x3f205f7,0x75aa10f,0x3e936c7,0x5188cee,0x5f,0xfff2fe92,0x708fd26,0x2341,0x2749ae8,0x6257695);
  printf("%w%V%b%s%V%m%J%s%R%H",0x4c7a950,0x5ec022e,0x4b9a7a4,0x2e278f4,0x6200e00,0x30,0xfffe8021,0x63b7839,0x900,0x42dc8d5,0x13d670a);
  printf("%w%V%b%s%V%m%J%s%R%H",0x47b6288,0x4f8f54e,0x142674c,0x8c3951,0x7037c99,100,0xfff0e101,0x3ecce47,10000,0x66fbc4d,0x37bed26);
  printf("%w%V%b%s%V%m%J%s%R%H",0x642ad16,0x740e6c5,0x258eae7,0x65e3017,0x586efb0,0x6a,0xffee03b9,0x4afb046,0x2be4,0x4c31ce0,0x52abb1f);
  printf("%w%V%b%s%V%m%J%s%R%H",0x55db7f6,0x6aef138,0x42210e5,0x72fd711,0xd8d75e,0x62,0xfff1e081,0x1ca0e4d,0x2584,0x325351f,0x69c0c21);
'''

import re
import math

args = []
for l in s.split('n'):
    if l == '':
        continue
    hexes = re.findall(r'(?:0x[0-9a-f]{2,8})|(?:d+)', l)
    arg = [hexes[5], hexes[6], hexes[8]]
    for i in range(len(arg)):
        if arg[i].startswith("0x"):
            arg[i] = int(arg[i], 16)
        else:
            arg[i] = int(arg[i])
    args.append(arg)
assert len(args) == 26

flag = []
for i in range(len(args)):
    for x in range(0x100):
        y2 = (args[i][2] + (x ** 3) + (args[i][1] - (x * args[i][0])))&0xFFFFFFFF
        y = int(math.sqrt(y2))
        if y**2 == y2 and y < 0x100:
            flag += [x, y]
            break
assert len(flag) == 52
print(bytes(flag))

flagbrics+{L0l_y0U_can_n0t_jus1_pArs3_th1s_buT_G0od_job}

至于输入,可以看到%p的函数是将参数逐个布置到arr上,依次用%p ASCII两字节为一组倒序输入即可。

楚慧杯 | scalar

这个题也是一道类似的printf虚拟机,在init_array同样能找到注册printf转换函数的函数:

RE新瓶装旧酒之printf虚拟机

其中arginfo返回0,代表不需要参数;sub_190Csub_1949分别返回了3和4,代表分别需要3/4个参数,且指定了其中的argtypes均为PA_INT。
看回main函数,这里需要输入64字节的小写十六进制,在对输入unhex以后以指定顺序传入printf中,format作为使用输入进行运算的指令序列。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int i; // [rsp+74h] [rbp-3Ch]
  int j; // [rsp+74h] [rbp-3Ch]
  int k; // [rsp+74h] [rbp-3Ch]
  int m; // [rsp+74h] [rbp-3Ch]
  int v8; // [rsp+78h] [rbp-38h]
  int v9; // [rsp+78h] [rbp-38h]
  int v10; // [rsp+7Ch] [rbp-34h]
  char v11; // [rsp+7Ch] [rbp-34h]
  unsigned int v12; // [rsp+7Ch] [rbp-34h]

  puts("Welcome to the chall! give me your pass!");
  __isoc99_scanf("%64s", input);
  if ( strlen(input) != 64 )
    return 0LL;
  for ( i = 0; i <= 63; ++i )
  {
    if ( (input[i] <= 47 || input[i] > 57) && (input[i] <= 96 || input[i] > 102) )
      return 0LL;
  }
  for ( j = 0; j <= 31; ++j )
  {
    if ( input[2 * j] <= 47 || input[2 * j] > 57 )
      v8 = input[2 * j] - 87;
    else
      v8 = input[2 * j] - 48;
    if ( input[2 * j + 1] <= 47 || input[2 * j + 1] > 57 )
      v10 = input[2 * j + 1] - 87;
    else
      v10 = input[2 * j + 1] - 48;
    unhex[j] = v10 | (16 * v8);
  }
  printf(
    format,
    (unsigned int)unhex[0],
    (unsigned int)unhex[1],
    (unsigned int)unhex[2],
    (unsigned int)unhex[2],
    (unsigned int)unhex[3],
    (unsigned int)unhex[4],
    (unsigned int)unhex[5],
    (unsigned int)unhex[5],
    (unsigned int)unhex[6],
    (unsigned int)unhex[7],
    (unsigned int)unhex[7],
    (unsigned int)unhex[8],
    (unsigned int)unhex[9],
    (unsigned int)unhex[10],
    (unsigned int)unhex[10],
    (unsigned int)unhex[11],
    (unsigned int)unhex[12],
    (unsigned int)unhex[13],
    (unsigned int)unhex[13],
    (unsigned int)unhex[14],
    (unsigned int)unhex[15],
    (unsigned int)unhex[15],
    (unsigned int)unhex[16],
    (unsigned int)unhex[17],
    (unsigned int)unhex[18],
    (unsigned int)unhex[18],
    (unsigned int)unhex[19],
    (unsigned int)unhex[20],
    (unsigned int)unhex[21],
    (unsigned int)unhex[22],
    (unsigned int)unhex[23],
    (unsigned int)unhex[23],
    (unsigned int)unhex[24],
    (unsigned int)unhex[25],
    (unsigned int)unhex[26],
    (unsigned int)unhex[26],
    (unsigned int)unhex[27],
    (unsigned int)unhex[28],
    (unsigned int)unhex[28],
    (unsigned int)unhex[29],
    (unsigned int)unhex[30],
    (unsigned int)unhex[31]);
  if ( unk_209D80 != 1LL )
    return 0LL;
  for ( k = 1; k <= 23; ++k )
  {
    if ( *(_QWORD *)&format[8 * k + 28000] )
      return 0LL;
  }
  puts("Correct!");
  for ( m = 0; m <= 15; ++m )
  {
    v9 = (unsigned __int8)(32 * LOBYTE(unhex[2 * m]) + unhex[2 * m + 1]);
    v11 = v9 & 0xF;
    if ( (v9 & 0xFu) > 9 )
      hex[2 * m] = v11 + 87;
    else
      hex[2 * m] = v11 + 48;
    v12 = (v9 >> 4) & 0xF;
    if ( v12 > 9 )
      hex[2 * m + 1] = v12 + 87;
    else
      hex[2 * m + 1] = v12 + 48;
  }
  printf("Here is your flag: %sn", hex);
  return 0LL;
}
通过对注册的转换函数逐个分析,将各格式占位符的功能整理如下:
'M': movl
'N': add
'O': sub
'P': mul
'Q': div
'R': mod
'S': shl
'T': shr
'U'xor
'V'and
'W'or
'X': load3
'Y': load4
'A': movq
其中load3、load4分别需要3个参数和4个参数,对应注册时的sub_190Csub_1949,代表将参数依次拼接转换为小端序的整数。
这些格式占位符对应的操作数基本都相同,使用printf_info中的信息进行指定,如%M
__int64 __fastcall func(FILE *stream, const struct printf_info *info, const void *const *args)
{
  int width; // [rsp+20h] [rbp-18h]
  int prec; // [rsp+24h] [rbp-14h]
  char *v6; // [rsp+28h] [rbp-10h]
  __int64 v7; // [rsp+30h] [rbp-8h]

  width = info->width;
  prec = info->prec;
  if ( (*((_BYTE *)info + 12) & 0x20) != 0 )
  {
    v6 = &format[width];
  }
  else if ( (*((_BYTE *)info + 12) & 0x40) != 0 )
  {
    v6 = &format[reg[width]];
  }
  else
  {
    v6 = (char *)&reg[width];
  }
  v7 = 0LL;
  if ( (*((_BYTE *)info + 13) & 2) != 0 )
  {
    v7 = *(int *)&format[prec];
  }
  else if ( (*((_BYTE *)info + 12) & 2) != 0 )
  {
    v7 = *(int *)&format[reg[prec]];
  }
  else if ( (*((_BYTE *)info + 12) & 1) != 0 )
  {
    v7 = prec;
  }
  else if ( (*((_BYTE *)info + 12) & 4) != 0 )
  {
    v7 = reg[prec];
  }
  *(_QWORD *)v6 = v7;
  return 0LL;
}
%M的作用是movl v6, v7,v6由info->width及相应标志位指定,v7由info->prec及相应标志位指定,关于printf_info的定义如下:
struct printf_info
{

  int prec;   /* Precision.  */
  int width;   /* Width.  */
  wchar_t spec;   /* Format letter.  */
  unsigned int is_long_double:1;/* L flag.  */
  unsigned int is_short:1/* h flag.  */
  unsigned int is_long:1/* l flag.  */
  unsigned int alt:1;  /* # flag.  */
  unsigned int space:1;  /* Space flag.  */
  unsigned int left:1;  /* - flag.  */
  unsigned int showsign:1/* + flag.  */
  unsigned int group:1;  /* ' flag.  */
  unsigned int extra:1;  /* For special use.  */
  unsigned int is_char:1/* hh flag.  */
  unsigned int wide:1;  /* Nonzero for wide character streams.  */
  unsigned int i18n:1;  /* I flag.  */
  unsigned int is_binary128:1/* Floating-point argument is ABI-compatible
       with IEC 60559 binary128.  */

  unsigned int __pad:3;  /* Unused so far.  */
  unsigned short int user; /* Bits for user-installed modifiers.  */
  wchar_t pad;   /* Padding character.  */
};
对应一下%M就是:
  width = info->width;
  prec = info->prec;
  if (info->left != 0) {
    v6 = &format[width];
  } else if (info->showsign != 0) {
    v6 = &format[reg[width]];
  } else {
    v6 = (char *)&reg[width];
  }
  v7 = 0LL;
  if (info->is_char != 0) {
    v7 = *(int *)&format[prec];
  } else if (info->is_short != 0) {
    v7 = *(int *)&format[reg[prec]];
  } else if (info->is_long_double != 0) {
    v7 = prec;
  } else if (info->is_long != 0) {
    v7 = reg[prec];
  }
  *(_QWORD *)v6 = v7;
那么可以写VM解释器了(开源在https://github.com/c10udlnk/myReverseExps/blob/main/VMprotect/printf_VMinterpreter.py),使用正则匹配format里所有的格式,直接判断是否有对应标志指定操作数:
import re

fm = "%0.27000llM%+0.1491575llM%0.4llN%+0.1863417llM%0.4llN%+0.1725080llM%0.4llN%+0.1009708llM%0.4llN%+0.39773llM%0.4llN%1.26000llM%3.2097151llM%0.X%0.3lV%+1.0lM%1.4llN%0.Y%0.5llT%0.3lV%+1.0lM%1.4llN%0.X%0.2llT%0.3lV%+1.0lM%1.4llN%0.Y%0.7llT%0.3lV%+1.0lM%1.4llN%0.Y%0.4llT%0.3lV%+1.0lM%1.4llN%0.X%0.1llT%0.3lV%+1.0lM%1.4llN%0.Y%0.6llT%0.3lV%+1.0lM%1.4llN%0.X%0.3llT%0.3lV%+1.0lM%1.4llN%0.X%0.3lV%+1.0lM%1.4llN%0.Y%0.5llT%0.3lV%+1.0lM%1.4llN%0.X%0.2llT%0.3lV%+1.0lM%1.4llN%0.Y%0.7llT%0.3lV%+1.0lM%1.4llN%29.28000llM%30.26000llM%31.27000llM%0.30hM%30.4llN%1.30hM%30.4llN%2.30hM%30.4llN%3.30hM%30.4llN%4.30hM%30.4llN%5.30hM%30.4llN%6.30hM%30.4llN%7.30hM%30.4llN%8.30hM%30.4llN%9.30hM%30.4llN%10.30hM%30.4llN%11.30hM%30.4llN%12.31hM%31.4llN%13.31hM%31.4llN%14.31hM%31.4llN%15.31hM%31.4llN%16.31hM%31.4llN%17.31hM%31.4llN%18.31hM%31.4llN%19.31hM%31.4llN%20.31hM%31.4llN%21.31hM%31.4llN%22.31hM%31.4llN%23.31hM%31.4llN%24.0llM%25.0lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.13lP%24.25lN%25.1lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.14lP%24.25lN%25.1lM%25.13lP%24.25lN%25.2lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.15lP%24.25lN%25.1lM%25.14lP%24.25lN%25.2lM%25.13lP%24.25lN%25.3lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.16lP%24.25lN%25.1lM%25.15lP%24.25lN%25.2lM%25.14lP%24.25lN%25.3lM%25.13lP%24.25lN%25.4lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.17lP%24.25lN%25.1lM%25.16lP%24.25lN%25.2lM%25.15lP%24.25lN%25.3lM%25.14lP%24.25lN%25.4lM%25.13lP%24.25lN%25.5lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.18lP%24.25lN%25.1lM%25.17lP%24.25lN%25.2lM%25.16lP%24.25lN%25.3lM%25.15lP%24.25lN%25.4lM%25.14lP%24.25lN%25.5lM%25.13lP%24.25lN%25.6lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.19lP%24.25lN%25.1lM%25.18lP%24.25lN%25.2lM%25.17lP%24.25lN%25.3lM%25.16lP%24.25lN%25.4lM%25.15lP%24.25lN%25.5lM%25.14lP%24.25lN%25.6lM%25.13lP%24.25lN%25.7lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.20lP%24.25lN%25.1lM%25.19lP%24.25lN%25.2lM%25.18lP%24.25lN%25.3lM%25.17lP%24.25lN%25.4lM%25.16lP%24.25lN%25.5lM%25.15lP%24.25lN%25.6lM%25.14lP%24.25lN%25.7lM%25.13lP%24.25lN%25.8lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.21lP%24.25lN%25.1lM%25.20lP%24.25lN%25.2lM%25.19lP%24.25lN%25.3lM%25.18lP%24.25lN%25.4lM%25.17lP%24.25lN%25.5lM%25.16lP%24.25lN%25.6lM%25.15lP%24.25lN%25.7lM%25.14lP%24.25lN%25.8lM%25.13lP%24.25lN%25.9lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.22lP%24.25lN%25.1lM%25.21lP%24.25lN%25.2lM%25.20lP%24.25lN%25.3lM%25.19lP%24.25lN%25.4lM%25.18lP%24.25lN%25.5lM%25.17lP%24.25lN%25.6lM%25.16lP%24.25lN%25.7lM%25.15lP%24.25lN%25.8lM%25.14lP%24.25lN%25.9lM%25.13lP%24.25lN%25.10lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.0lM%25.23lP%24.25lN%25.1lM%25.22lP%24.25lN%25.2lM%25.21lP%24.25lN%25.3lM%25.20lP%24.25lN%25.4lM%25.19lP%24.25lN%25.5lM%25.18lP%24.25lN%25.6lM%25.17lP%24.25lN%25.7lM%25.16lP%24.25lN%25.8lM%25.15lP%24.25lN%25.9lM%25.14lP%24.25lN%25.10lM%25.13lP%24.25lN%25.11lM%25.12lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.1lM%25.23lP%24.25lN%25.2lM%25.22lP%24.25lN%25.3lM%25.21lP%24.25lN%25.4lM%25.20lP%24.25lN%25.5lM%25.19lP%24.25lN%25.6lM%25.18lP%24.25lN%25.7lM%25.17lP%24.25lN%25.8lM%25.16lP%24.25lN%25.9lM%25.15lP%24.25lN%25.10lM%25.14lP%24.25lN%25.11lM%25.13lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.2lM%25.23lP%24.25lN%25.3lM%25.22lP%24.25lN%25.4lM%25.21lP%24.25lN%25.5lM%25.20lP%24.25lN%25.6lM%25.19lP%24.25lN%25.7lM%25.18lP%24.25lN%25.8lM%25.17lP%24.25lN%25.9lM%25.16lP%24.25lN%25.10lM%25.15lP%24.25lN%25.11lM%25.14lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.3lM%25.23lP%24.25lN%25.4lM%25.22lP%24.25lN%25.5lM%25.21lP%24.25lN%25.6lM%25.20lP%24.25lN%25.7lM%25.19lP%24.25lN%25.8lM%25.18lP%24.25lN%25.9lM%25.17lP%24.25lN%25.10lM%25.16lP%24.25lN%25.11lM%25.15lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.4lM%25.23lP%24.25lN%25.5lM%25.22lP%24.25lN%25.6lM%25.21lP%24.25lN%25.7lM%25.20lP%24.25lN%25.8lM%25.19lP%24.25lN%25.9lM%25.18lP%24.25lN%25.10lM%25.17lP%24.25lN%25.11lM%25.16lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.5lM%25.23lP%24.25lN%25.6lM%25.22lP%24.25lN%25.7lM%25.21lP%24.25lN%25.8lM%25.20lP%24.25lN%25.9lM%25.19lP%24.25lN%25.10lM%25.18lP%24.25lN%25.11lM%25.17lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.6lM%25.23lP%24.25lN%25.7lM%25.22lP%24.25lN%25.8lM%25.21lP%24.25lN%25.9lM%25.20lP%24.25lN%25.10lM%25.19lP%24.25lN%25.11lM%25.18lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.7lM%25.23lP%24.25lN%25.8lM%25.22lP%24.25lN%25.9lM%25.21lP%24.25lN%25.10lM%25.20lP%24.25lN%25.11lM%25.19lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.8lM%25.23lP%24.25lN%25.9lM%25.22lP%24.25lN%25.10lM%25.21lP%24.25lN%25.11lM%25.20lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.9lM%25.23lP%24.25lN%25.10lM%25.22lP%24.25lN%25.11lM%25.21lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.10lM%25.23lP%24.25lN%25.11lM%25.22lP%24.25lN%+29.24lM%29.8llN%24.0llM%25.11lM%25.23lP%24.25lN%+29.24lM%29.8llN%29.28000llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28016llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28032llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28048llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28064llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28080llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28096llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28112llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28128llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28144llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28160llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28176llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28008llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28024llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28040llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28056llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28072llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28088llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28104llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28120llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28136llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28152llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28168llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28088llM%30.28184llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28080llM%30.28176llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28072llM%30.28168llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28064llM%30.28160llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28056llM%30.28152llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28048llM%30.28144llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28048llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28064llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28080llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28096llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28112llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28128llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28056llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28072llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28088llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28104llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28120llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28040llM%30.28136llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28032llM%30.28128llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28024llM%30.28120llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28016llM%30.28112llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28008llM%30.28104llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28000llM%30.28096llM%0.30hA%31.0lM%1.29hA%31.666643llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.470296llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.654183llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.997805llP%1.31lO%+29.1lM%29.8llN%31.0lM%1.29hA%31.136657llP%1.31lN%+29.1lM%29.8llN%31.0lM%1.29hA%31.683901llP%1.31lO%+29.1lM%29.8llN%+30.0llM%29.28000llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28016llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28032llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28048llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28064llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28080llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28008llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28024llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28040llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28056llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28072llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM%29.28088llM%0.29hA%2.0lM%0.1048576llN%0.21llT%29.8llN%1.29hA%1.0lN%+29.1lM%0.21llS%2.0lO%29.8llO%+29.2lM"
ins_set = {'M'"movl  {0}, {1}",
           'N'"add   {0}, {1}",
           'O'"sub   {0}, {1}",
           'P'"mul   {0}, {1}",
           'Q'"div   {0}, {1}",
           'R'"mod   {0}, {1}",
           'S'"shl   {0}, {1}",
           'T'"shr   {0}, {1}",
           'U'"xor   {0}, {1}",
           'V'"and   {0}, {1}",
           'W'"or    {0}, {1}",
           'X'"load3 {0}",
           'Y'"load4 {0}",
           'A'"movq  {0}, {1}",
}

output = open('assembly.txt''w')
pc = 0
ptn = r'%(?P<flags>[#+-0]*)(?P<width>d*).?(?P<precision>d*)(?P<length>[hljztqL]*)(?P<specifier>[AM-Z])'
while pc < len(fm):
    reobj = re.search(ptn, fm[pc:])
    try:
        d = reobj.groupdict()
        # default
        d["width"] = '0' if d["width"] == '' else d["width"]
        d["precision"] = '-1' if d["precision"] == '' else d["precision"]
        # v6
        if '-' in d["flags"]:
            a = "fm[{0}]".format(d["width"])
        elif '+' in d["flags"]:
            a = "fm[r{0}]".format(d["width"])
        else:
            a = "r{0}".format(d["width"])
        # v7
        if d["specifier"not in "XY":
            if 'hh' in d["length"]:
                b = "fm[{0}]".format(d["precision"])
            elif 'h' in d["length"]:
                b = "fm[r{0}]".format(d["precision"])
            elif 'L' in d["length"]:
                b = "{0}".format(d["precision"])
            elif 'l' in d["length"]:
                b = "r{0}".format(d["precision"])
            else:
                b = "0"
            args = (a, b)
        else:
            args = (a,)
        output.write(ins_set[d["specifier"]].format(*args) + 'n')
        pc += reobj.end()
    except Exception as e:
        print("Something wrong?:", repr(e))
        break
得到1701行的汇编(太长了,这里就不放了,跑一遍上面的脚本就有)。
阅读可知,format的26000偏移开始以一定的运算存入了输入;format的27000偏移开始存入了已知数组;format的28000偏移开始存的是输出,在main函数中会进行检验,如小端序(24字节)后为1则输入正确。
简单来说,这段汇编就是实现了一个ED25519曲线的标量乘法(scalar multiplication),用C表示前文的汇编大概是(对应汇编的部分见注释):
/*
Input:
  a[0]+256*a[1]+...+256^31*a[31] = a
  b[0]+256*b[1]+...+256^31*b[31] = b

Output:
  s[0]+256*s[1]+...+256^31*s[31] = (ab) mod l
  where l = 2^252 + 27742317777372353535851937790883648493.
*/

void sc_mul(unsigned char *s, const unsigned char *a, const unsigned char *b) {
  // int64_t a0 = 2097151 & load_3(a);
  // int64_t a1 = 2097151 & (load_4(a + 2) >> 5);
  // int64_t a2 = 2097151 & (load_3(a + 5) >> 2);
  // int64_t a3 = 2097151 & (load_4(a + 7) >> 7);
  // int64_t a4 = 2097151 & (load_4(a + 10) >> 4);
  // int64_t a5 = 2097151 & (load_3(a + 13) >> 1);
  // int64_t a6 = 2097151 & (load_4(a + 15) >> 6);
  // int64_t a7 = 2097151 & (load_3(a + 18) >> 3);
  // int64_t a8 = 2097151 & load_3(a + 21);
  // int64_t a9 = 2097151 & (load_4(a + 23) >> 5);
  // int64_t a10 = 2097151 & (load_3(a + 26) >> 2);
  // int64_t a11 = (load_4(a + 28) >> 7);
  // 这里已知数组a直接赋值,没有进行如上运算
  a0 = 1491575;
  a1 = 1863417;
  a2 = 1725080;
  a3 = 1009708;
  a4 = 39773;
  a5 = 0;
  a6 = 0;
  a7 = 0;
  a8 = 0;
  a9 = 0;
  a10 = 0;
  a11 = 0;
  
  // 汇编第12行开始,b存的是一定运算后的输入;b+2之类的偏移通过控制printf的输入参数控制,即:
  // unhex[0], unhex[1], unhex[2],
  // unhex[2], unhex[3], unhex[4], unhex[5],
  // unhex[5], unhex[6], unhex[7],
  // unhex[7], unhex[8], unhex[9], unhex[10],
  // unhex[10], unhex[11], unhex[12], unhex[13],
  // unhex[13], unhex[14], unhex[15], 
  // unhex[15], unhex[16], unhex[17], unhex[18],
  // unhex[18], unhex[19], unhex[20],
  // unhex[21], unhex[22], unhex[23],
  // unhex[23], unhex[24], unhex[25], unhex[26],
  // unhex[26], unhex[27], unhex[28],
  // unhex[28], unhex[29], unhex[30], unhex[31]
  int64_t b0 = 2097151 & load_3(b);
  int64_t b1 = 2097151 & (load_4(b + 2) >> 5);
  int64_t b2 = 2097151 & (load_3(b + 5) >> 2);
  int64_t b3 = 2097151 & (load_4(b + 7) >> 7);
  int64_t b4 = 2097151 & (load_4(b + 10) >> 4);
  int64_t b5 = 2097151 & (load_3(b + 13) >> 1);
  int64_t b6 = 2097151 & (load_4(b + 15) >> 6);
  int64_t b7 = 2097151 & (load_3(b + 18) >> 3);
  int64_t b8 = 2097151 & load_3(b + 21);
  int64_t b9 = 2097151 & (load_4(b + 23) >> 5);
  int64_t b10 = 2097151 & (load_3(b + 26) >> 2);
  int64_t b11 = (load_4(b + 28) >> 7);
  int64_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, s21, s22, s23, carry0, carry1, carry2, carry3, carry4, carry5, carry6, carry7, carry8, carry9, carry10, carry11, carry12, carry13, carry14, carry15, carry16, carry17, carry18, carry19, carry20, carry21, carry22;

  // 汇编第123行开始
  s0 = a0*b0;
  s1 = (a0*b1 + a1*b0);
  s2 = (a0*b2 + a1*b1 + a2*b0);
  s3 = (a0*b3 + a1*b2 + a2*b1 + a3*b0);
  s4 = (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0);
  s5 = (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0);
  s6 = (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0);
  s7 = (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0);
  s8 = (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0);
  s9 = (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0);
  s10 = (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0);
  s11 = (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0);
  s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1);
  s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2);
  s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3);
  s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4);
  s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5);
  s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6);
  s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7);
  s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8);
  s20 = (a9*b11 + a10*b10 + a11*b9);
  s21 = (a10*b11 + a11*b10);
  s22 = a11*b11;
  s23 = 0;

  // 汇编第624行开始
  carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
  carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
  carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
  carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
  carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
  carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
  carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
  carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
  carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21;
  carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21;
  carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21;
  carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21;

  // 汇编第780行开始
  carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
  carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
  carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
  carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
  carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
  carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
  carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
  carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21;
  carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21;
  carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21;
  carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21;

  // 汇编第923行开始
  s11 += s23 * 666643;
  s12 += s23 * 470296;
  s13 += s23 * 654183;
  s14 -= s23 * 997805;
  s15 += s23 * 136657;
  s16 -= s23 * 683901;
  s23 = 0;
  s10 += s22 * 666643;
  s11 += s22 * 470296;
  s12 += s22 * 654183;
  s13 -= s22 * 997805;
  s14 += s22 * 136657;
  s15 -= s22 * 683901;
  s22 = 0;
  s9 += s21 * 666643;
  s10 += s21 * 470296;
  s11 += s21 * 654183;
  s12 -= s21 * 997805;
  s13 += s21 * 136657;
  s14 -= s21 * 683901;
  s21 = 0;
  s8 += s20 * 666643;
  s9 += s20 * 470296;
  s10 += s20 * 654183;
  s11 -= s20 * 997805;
  s12 += s20 * 136657;
  s13 -= s20 * 683901;
  s20 = 0;
  s7 += s19 * 666643;
  s8 += s19 * 470296;
  s9 += s19 * 654183;
  s10 -= s19 * 997805;
  s11 += s19 * 136657;
  s12 -= s19 * 683901;
  s19 = 0;
  s6 += s18 * 666643;
  s7 += s18 * 470296;
  s8 += s18 * 654183;
  s9 -= s18 * 997805;
  s10 += s18 * 136657;
  s11 -= s18 * 683901;
  s18 = 0;

  // 汇编第1163行开始
  carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
  carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
  carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;
  carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21;
  carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21;
  carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21;

  // 汇编第1241行开始
  carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
  carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
  carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
  carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21;
  carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21;

  // 汇编第1306行开始
  s5 += s17 * 666643;
  s6 += s17 * 470296;
  s7 += s17 * 654183;
  s8 -= s17 * 997805;
  s9 += s17 * 136657;
  s10 -= s17 * 683901;
  s17 = 0;
  s4 += s16 * 666643;
  s5 += s16 * 470296;
  s6 += s16 * 654183;
  s7 -= s16 * 997805;
  s8 += s16 * 136657;
  s9 -= s16 * 683901;
  s16 = 0;
  s3 += s15 * 666643;
  s4 += s15 * 470296;
  s5 += s15 * 654183;
  s6 -= s15 * 997805;
  s7 += s15 * 136657;
  s8 -= s15 * 683901;
  s15 = 0;
  s2 += s14 * 666643;
  s3 += s14 * 470296;
  s4 += s14 * 654183;
  s5 -= s14 * 997805;
  s6 += s14 * 136657;
  s7 -= s14 * 683901;
  s14 = 0;
  s1 += s13 * 666643;
  s2 += s13 * 470296;
  s3 += s13 * 654183;
  s4 -= s13 * 997805;
  s5 += s13 * 136657;
  s6 -= s13 * 683901;
  s13 = 0;
  s0 += s12 * 666643;
  s1 += s12 * 470296;
  s2 += s12 * 654183;
  s3 -= s12 * 997805;
  s4 += s12 * 136657;
  s5 -= s12 * 683901;
  s12 = 0;

  // 汇编第1546行开始
  carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21;
  carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21;
  carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21;
  carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21;
  carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21;
  carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21;

  // 汇编第1624行开始
  carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21;
  carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21;
  carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21;
  carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21;
  carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21;
  carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
  // 这里s0-s23的24字节就是main中检验的数组,如果s0-s23小端序转换后为1那么这里的结果s也为1
  // s[0] = s0 >> 0;
  // s[1] = s0 >> 8;
  // s[2] = (s0 >> 16) | (s1 << 5);
  // s[3] = s1 >> 3;
  // s[4] = s1 >> 11;
  // s[5] = (s1 >> 19) | (s2 << 2);
  // s[6] = s2 >> 6;
  // s[7] = (s2 >> 14) | (s3 << 7);
  // s[8] = s3 >> 1;
  // s[9] = s3 >> 9;
  // s[10] = (s3 >> 17) | (s4 << 4);
  // s[11] = s4 >> 4;
  // s[12] = s4 >> 12;
  // s[13] = (s4 >> 20) | (s5 << 1);
  // s[14] = s5 >> 7;
  // s[15] = (s5 >> 15) | (s6 << 6);
  // s[16] = s6 >> 2;
  // s[17] = s6 >> 10;
  // s[18] = (s6 >> 18) | (s7 << 3);
  // s[19] = s7 >> 5;
  // s[20] = s7 >> 13;
  // s[21] = s8 >> 0;
  // s[22] = s8 >> 8;
  // s[23] = (s8 >> 16) | (s9 << 5);
  // s[24] = s9 >> 3;
  // s[25] = s9 >> 11;
  // s[26] = (s9 >> 19) | (s10 << 2);
  // s[27] = s10 >> 6;
  // s[28] = (s10 >> 14) | (s11 << 7);
  // s[29] = s11 >> 1;
  // s[30] = s11 >> 9;
  // s[31] = s11 >> 17;
  return;
}
那么本题的思路就是用a0-a11还原出原本的乘数a,通过

反推出乘数b即可。

写Sage脚本:
l = 2^252 + 27742317777372353535851937790883648493
tmp = [149157518634171725080100970839773]
a = 0
for i in range(len(tmp)):
    a += tmp[i] << (i * 21# a0-a11实际上是从a依次取21位
b = a.inverse_mod(l)
assert (a * b) % l == 1
print(int(b).to_bytes(32'little').hex())
# 307e2dfb95b36f6389785f7713868f98367980d12b14a2e3f9021d957c39670b
将结果输进程序中即可获得flag:

RE新瓶装旧酒之printf虚拟机

flage7b9353489756e87931d473222539bbe

原文始发于微信公众号(山石网科安全技术研究院):RE新瓶装旧酒之printf虚拟机

版权声明:admin 发表于 2024年3月6日 下午1:47。
转载请注明:RE新瓶装旧酒之printf虚拟机 | CTF导航

相关文章