题目链接
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中
(+汇编层级的返回值顺序和编程语言级别的顺序是一样的还是相反的?)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。