题目链接

https://adworld.xctf.org.cn/media/file/task/434e2ae5fa614db3b...

这个可执行文件是使用go语言编写的,所以在高级的数据抽象上,和c语言有出入。但是只要架构不变,汇编代码是通用的

[woc@eviliage test]$ ./easygo 
Please input you flag like flag{123} to judge:
flag{aaa} 
Try again! Come on!
[woc@eviliage test]$ checksec --file=./easygo
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols             FORTIFY Fortified       Fortifiable     FILE
No RELRO        No canary found   NX enabled    No PIE          N/A        N/A          No Symbols  N/A     0               0               ./easygo

程序没有PIE,执行流程也比较简介,可以初步确定为加密类题目

关键函数定位,得出答案

// main.main
void __fastcall main_main()
{
  __int64 v0; // rdx
  __int64 v1; // rbx
  __int64 v2; // [rsp+8h] [rbp-F8h]
  __int64 v3; // [rsp+10h] [rbp-F0h]
  __int64 v4; // [rsp+10h] [rbp-F0h]
  __int64 v5; // [rsp+18h] [rbp-E8h]
  __int64 v6; // [rsp+18h] [rbp-E8h]
  __int64 v7; // [rsp+20h] [rbp-E0h]
  __int128 v8; // [rsp+20h] [rbp-E0h]
  __int64 v9; // [rsp+28h] [rbp-D8h]
  __int64 v10; // [rsp+30h] [rbp-D0h]
  __int64 v11; // [rsp+38h] [rbp-C8h]
  __int64 v12; // [rsp+50h] [rbp-B0h]
  __int64 v13[4]; // [rsp+58h] [rbp-A8h] BYREF
  __int64 v14; // [rsp+78h] [rbp-88h] BYREF
  __int64 v15; // [rsp+98h] [rbp-68h]
  string *p_string; // [rsp+A0h] [rbp-60h]
  __int64 v17[2]; // [rsp+A8h] [rbp-58h] BYREF
  __int64 v18[2]; // [rsp+B8h] [rbp-48h] BYREF
  __int64 v19[2]; // [rsp+C8h] [rbp-38h] BYREF
  __int64 v20[2]; // [rsp+D8h] [rbp-28h] BYREF
  __int64 v21[2]; // [rsp+E8h] [rbp-18h] BYREF

  p_string = (string *)runtime_newobject((__int64)&RTYPE_string);
  v21[0] = (__int64)&RTYPE_string;
  v21[1] = (__int64)&off_4E1130;
  fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_572B18, (__int64)v21, 1LL, 1LL);
  v20[0] = (__int64)&RTYPE__ptr_string;
  v20[1] = (__int64)p_string;
  v11 = fmt_Fscanf(
          (__int64)&go_itab__ptr_os_File_comma__ptr_io_Reader,
          qword_572B10,
          (__int64)"%s",
          2LL,
          (__int64)v20,
          1LL,
          1LL);
  v5 = runtime_stringtoslicebyte(
         (__int64)&v14,
         (__int64)"tGRBtXMZgD6ZhalBtCUTgWgZfnkTgqoNsnAVsmUYsGtCt9pEtDEYsql3",
         56LL);            //这个字符串很刺眼,结合base64加密的特点,这大概就是密文了
  v8 = runtime_slicebytetostring((__int64)v13, v5, v7, v9);
  v6 = encoding_base64__ptr_Encoding_DecodeString(qword_572B00, v8, *((__int64 *)&v8 + 1));        //第一个参数很奇怪,但是我们可以猜出2、3个参数是待加密字符串的地址和长度(第三个参数由存放字符串指针的位置+8得到,很可能是go语言的字符串结构)
  v0 = v6;
  v1 = v8;
  if ( v10 )        // 为什么这里有一个不明的 if(v10)? 这个块中没有出现v10,或许是全局变量
  {
    v15 = v6;
    v12 = v8;
    v2 = (*(__int64 (__golang **)(__int64))(v10 + 24))(v11);
    v4 = runtime_convTstring(v2, v3);
    v19[0] = (__int64)&RTYPE_string;
    v19[1] = v4;
    fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_572B18, (__int64)v19, 1LL, 1LL);
    v0 = v15;
    v1 = v12;
  }
  if ( p_string->len == v1 && runtime_memequal(v0, (__int64)p_string->ptr, v1) )            //成功的判断,长度要相等,并使用memequal函数,可以猜测:v0是解密后的字符串指针,p_string->ptr是输入的字符串,v1是比较的字符数量
  {
    v18[0] = (__int64)&RTYPE_string;
    v18[1] = (__int64)&off_4E1140;
    fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_572B18, (__int64)v18, 1LL, 1LL);
  }
  else
  {
    v17[0] = (__int64)&RTYPE_string;
    v17[1] = (__int64)&off_4E1150;
    fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_572B18, (__int64)v17, 1LL, 1LL);
  }
}

(idaPro的图形模式能够提示一些反编译模式中没有显示的字符串,所以记得两个视图结合分析)

进入到base解密函数后发现其结构非常复杂,这很可能是一个库函数。那么下一步的思路,要么是查阅相关文档,要么直接暴力使用gdb调试,在判断步骤下断点。

go语言编译的程序似乎没有符号表,在objdump后,不能直接通过搜索函数名称来定位。但好在没有pie保护,通过地址也能直接定位,否而可能就要搜索字节码了

直接在memeqeual处下断点:

Breakpoint 1 at 0x4953a1
(gdb) r
Starting program: /test/easygo 

Please input you flag like flag{123} to judge:
flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}

Thread 1 "easygo" hit Breakpoint 1, 0x00000000004953a1 in ?? ()
(gdb) info register
rax            0xc0000981d0        824634343888
rbx            0x2a                42
rcx            0xc0000b2060        824634450016
rdx            0xc0000b2090        824634450064
rsi            0xc0000b2090        824634450064
rdi            0x38                56
rbp            0xc0000aaf88        0xc0000aaf88
rsp            0xc0000aae90        0xc0000aae90
r8             0x0                 0
r9             0x0                 0
r10            0x2a                42
r11            0x2a                42
r12            0xc0000b2090        824634450064
r13            0xc0000ae580        824634434944
r14            0x2a                42
r15            0x40                64
rip            0x4953a1            0x4953a1
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
fs_base        0x573990            5716368
gs_base        0x0                 0
(gdb) x/s 0xc0000b2060
0xc0000b2060:   "flag{", 'a' <repeats 36 times>, "}"
(gdb) x/s 0xc0000b2090
0xc0000b2090:   "flag{92094daf-33c9-431e-a85a-8bfbd5df98ad}"

结合静态分析的代码段,我们就看%rcx和%rdx中的数据,就能得到解密的数据(注意:因为短路求值,在判断字符串长度不相等的时候不会进入后一步,我们可以通过base64加密的原理反推出长度:56 *3/4 =42,但这其中包含了flag{}这六个无用字符,所以填充36个任意字符就行)

汇编层级的思考

  4952dd:    48 89 04 24              mov    %rax,(%rsp)
  4952e1:    48 89 4c 24 08           mov    %rcx,0x8(%rsp)
  4952e6:    48 89 54 24 10           mov    %rdx,0x10(%rsp)
  4952eb:    e8 30 93 fe ff           call   0x47e620
//      call encodeBase64Str
  4952f0:    48 8b 44 24 38           mov    0x38(%rsp),%rax
  4952f5:    48 8b 4c 24 30           mov    0x30(%rsp),%rcx
  4952fa:    48 8b 54 24 18           mov    0x18(%rsp),%rdx
  4952ff:    48 8b 5c 24 20           mov    0x20(%rsp),%rbx

我们可以看到,rax寄存器也可以存储函数的参数!
另外,我们还可以看到go语言是如何处理多返回值的,并不是像c一样仅仅限制在rax中
(+汇编层级的返回值顺序和编程语言级别的顺序是一样的还是相反的?)

一些待学习的资料

1.go语言逆向初级
2.正向角度看go逆向


StupidMagpie
6 声望0 粉丝