(感觉我做的很复杂,可能不是出题者的本意)
对于pwn,不知道啥意思,百度一下,大概的意思是:
1 、程序运行在服务器端,并构建一个类似tomcat的服务,并将服务的IP和端口提供出来,用户使用telnet等直接连接这个IP和端口后服务器就启动这个PWN进 程,从而进行交互。
2、运行在PWN的程序存在漏洞,客户端使用和服务交互的数据,来利用这个漏洞,从而来拿到服务器的shell。
3、flag已文件存储在服务器的系统上,通过ls来查找falg.txt,从而拿到flag。
一、安装环境
1、题目给出是服务器运行的环境是
ubuntu16.04 4.4.0-91-generic,在网上没找到对应的版本,直接下了16.04的版本安装。
大版本相同,类似libc.so这样系统库,应该不会变化。因此此时认为我的系统的libc.so与服务器的libc.so是一致的。
2、安装pwntools工具
好像做PWN,大家都用这个工具,之前没用过,先安装再说。直接运行如下命令:
1 、程序运行在服务器端,并构建一个类似tomcat的服务,并将服务的IP和端口提供出来,用户使用telnet等直接连接这个IP和端口后服务器就启动这个PWN进 程,从而进行交互。
2、运行在PWN的程序存在漏洞,客户端使用和服务交互的数据,来利用这个漏洞,从而来拿到服务器的shell。
3、flag已文件存储在服务器的系统上,通过ls来查找falg.txt,从而拿到flag。
一、安装环境
1、题目给出是服务器运行的环境是
ubuntu16.04 4.4.0-91-generic,在网上没找到对应的版本,直接下了16.04的版本安装。
大版本相同,类似libc.so这样系统库,应该不会变化。因此此时认为我的系统的libc.so与服务器的libc.so是一致的。
2、安装pwntools工具
好像做PWN,大家都用这个工具,之前没用过,先安装再说。直接运行如下命令:
sudo pip install pwntools
sudo pip install pwntools
如果没有安装pip,需要安装下pip
sudo apt-get install python-pip
对于安装
pwntools如果pip的版本太低中间会出错,因此需要将pip升级到最高版本。执行如下命令:
pip install --upgrade pip
直接在命令行输入:
checksec 后出现如下提示就表示pwntools安装成功了。
oooaooo@oooaooo-virtual-machine:~$ checksec
usage: pwn checksec [-h] [--file [elf [elf ...]]] [elf [elf ...]]
oooaooo@oooaooo-virtual-machine:~$
pip install --upgrade pip
直接在命令行输入:
checksec 后出现如下提示就表示pwntools安装成功了。
oooaooo@oooaooo-virtual-machine:~$ checksec
usage: pwn checksec [-h] [--file [elf [elf ...]]] [elf [elf ...]]
oooaooo@oooaooo-virtual-machine:~$
oooaooo@oooaooo-virtual-machine:~$ checksec
usage: pwn checksec [-h] [--file [elf [elf ...]]] [elf [elf ...]]
oooaooo@oooaooo-virtual-machine:~$
二、IDA远程调试liunx(本程序是64位的使用ida64.exe调试)
二、IDA远程调试liunx(本程序是64位的使用ida64.exe调试)
二、IDA远程调试liunx(本程序是64位的使用ida64.exe调试)
1、将IDA目录的linux_server64文件拷贝到ubunto中,并使其运行。
2、打开IDA,选择"debuger->run->remote linux debugger"
3、配置如下:
点击OK就可以了,注意ida64要以管理员权限运行。
三、WOW程序分析
先不忙调试,看看程序架构 IDA载入后 如下:main函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
welcome(v3, 0LL);
JUMPOUT(__CS__, 4196376LL);
}
1、将IDA目录的linux_server64文件拷贝到ubunto中,并使其运行。
2、打开IDA,选择"debuger->run->remote linux debugger"
3、配置如下:
1、将IDA目录的linux_server64文件拷贝到ubunto中,并使其运行。
2、打开IDA,选择"debuger->run->remote linux debugger"
3、配置如下:
点击OK就可以了,注意ida64要以管理员权限运行。
三、WOW程序分析
先不忙调试,看看程序架构 IDA载入后 如下:main函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
welcome(v3, 0LL);
JUMPOUT(__CS__, 4196376LL);
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
welcome(v3, 0LL);
JUMPOUT(__CS__, 4196376LL);
}
从中可以看出最后一条语句,IDA 分析失败了:JUMPOUT(__CS__, 4196376LL) 先不管,看下welcome函数
从中可以看出最后一条语句,IDA 分析失败了:JUMPOUT(__CS__, 4196376LL) 先不管,看下welcome函数
1、welcome函数(过反调试)
__int64 welcome()
{
puts("***************************\n");
puts("2018 kanxue CTF\n");
puts("***************************\n");
puts("***************************\n");
puts(&byte_400B78);
puts(&byte_400BA8);
puts(&byte_400BD8);
puts(asc_400C01);
puts("***************************\n");
return test("***************************\n");
}
__int64 welcome()
{
puts("***************************\n");
puts("2018 kanxue CTF\n");
puts("***************************\n");
puts("***************************\n");
puts(&byte_400B78);
puts(&byte_400BA8);
puts(&byte_400BD8);
puts(asc_400C01);
puts("***************************\n");
return test("***************************\n");
}
是往终端上输出字符(输出了一首诗)
这里面需要注意的test函数。
text:0000000000400751
.text:0000000000400751 public test
.text:0000000000400751 test proc near ; CODE XREF: welcome+63↑p
.text:0000000000400751 ; __unwind {
.text:0000000000400751 push rbp
.text:0000000000400752 mov rbp, rsp
.text:0000000000400755 mov rax, 65h
.text:000000000040075C mov rcx, 0
.text:0000000000400763 mov rdx, 1 ; addr
.text:000000000040076A mov rsi, 0 ; pid
.text:0000000000400771 mov rdi, 0 ; request
.text:0000000000400778 syscall ; LINUX - sys_ptrace
.text:000000000040077A cmp rax, 0
.text:000000000040077E jge short L_K1
.text:0000000000400780 mov rax, 3Ch
.text:0000000000400787 mov rdi, 0 ; error_code
.text:000000000040078E syscall ; LINUX - sys_exit
.text:0000000000400790
.text:0000000000400790 L_K1: ; CODE XREF: test+2D↑j
.text:0000000000400790 nop
.text:0000000000400791 nop
.text:0000000000400792 pop rbp
.text:0000000000400793 retn
.text:0000000000400793 ; } // starts at 400751
.text:0000000000400793 test endp
这个函数实际上是个反调试函数。liunx程序如果要调试,必须要使用ptrace函数来实现,包括IDA等。程序先ptrace自己,此时IDA将无法附加调试。
text:0000000000400751
.text:0000000000400751 public test
.text:0000000000400751 test proc near ; CODE XREF: welcome+63↑p
.text:0000000000400751 ; __unwind {
.text:0000000000400751 push rbp
.text:0000000000400752 mov rbp, rsp
.text:0000000000400755 mov rax, 65h
.text:000000000040075C mov rcx, 0
.text:0000000000400763 mov rdx, 1 ; addr
.text:000000000040076A mov rsi, 0 ; pid
.text:0000000000400771 mov rdi, 0 ; request
.text:0000000000400778 syscall ; LINUX - sys_ptrace
.text:000000000040077A cmp rax, 0
.text:000000000040077E jge short L_K1
.text:0000000000400780 mov rax, 3Ch
.text:0000000000400787 mov rdi, 0 ; error_code
.text:000000000040078E syscall ; LINUX - sys_exit
.text:0000000000400790
.text:0000000000400790 L_K1: ; CODE XREF: test+2D↑j
.text:0000000000400790 nop
.text:0000000000400791 nop
.text:0000000000400792 pop rbp
.text:0000000000400793 retn
.text:0000000000400793 ; } // starts at 400751
.text:0000000000400793 test endp
这个函数实际上是个反调试函数。liunx程序如果要调试,必须要使用ptrace函数来实现,包括IDA等。程序先ptrace自己,此时IDA将无法附加调试。
如果在IDA已经调试程序的状态下,吱声跳跃ptrace,那么返回值是失败的,此时程序调用exit来结束自身。
因此在IDA运行到这里后直接将相关代码nop掉就可以过反调试了。
2、花指令
程序加入了一些花指令,使IDA分析失效,主要形式是:
.text:000000000040080C E8 00 00 00 00 call $+5
.text:0000000000400811
.text:0000000000400811 L_64:
.text:0000000000400811 58 pop rax
.text:0000000000400812 48 83 C0 07 add rax, 7
.text:0000000000400816 FF E0 jmp rax
可以使用脚本直接将上面的代码nop掉就可以过掉花指令,由于时间比较紧,就先没做这部分。
.text:000000000040080C E8 00 00 00 00 call $+5
.text:0000000000400811
.text:0000000000400811 L_64:
.text:0000000000400811 58 pop rax
.text:0000000000400812 48 83 C0 07 add rax, 7
.text:0000000000400816 FF E0 jmp rax
可以使用脚本直接将上面的代码nop掉就可以过掉花指令,由于时间比较紧,就先没做这部分。
3、SMC
下面是对main函数后面代码分析
text:00000000004007E9 call _setbuf
.text:00000000004007EE mov rax, cs:stdout@@GLIBC_2_2_5
.text:00000000004007F5 mov esi, 0 ; buf
.text:00000000004007FA mov rdi, rax ; stream
.text:00000000004007FD call _setbuf
.text:0000000000400802 mov eax, 0
.text:0000000000400807 call welcome
//花指令
.text:000000000040080C call $+5
.text:0000000000400811
.text:0000000000400811 L_64:
.text:0000000000400811 pop rax
.text:0000000000400812 add rax, 7
.text:0000000000400816 jmp rax
.text:0000000000400816 main endp
.text:0000000000400816
.text:0000000000400818 ; ---------------------------------------------------------------------------
//调用mprotect将0x400000 大小0x1000属性改成可读可写可执行
.text:0000000000400818 sar rax, 0Ch
.text:000000000040081C shl rax, 0Ch
.text:0000000000400820 mov rdi, rax
.text:0000000000400823 mov rdx, 7
.text:000000000040082A mov rax, 0Ah
.text:0000000000400831 mov rsi, 1000h
.text:0000000000400838 syscall ; LINUX - sys_mprotect
//从终端读取6个字符到全局变量szCh中
.text:000000000040083A xor rax, rax
.text:000000000040083D mov rdx, 6
.text:0000000000400844 push rax
.text:0000000000400845 lea rax, szCh
.text:000000000040084D mov rsi, rax
.text:0000000000400850 pop rax
.text:0000000000400851 mov rdi, rax
.text:0000000000400854 syscall ; LINUX -
//使用输入的第一个字符,与40087F地址开始进行亦或,并写回到40087F相关内容中
//直到遇到亦或的结果为0x90结束。
.text:0000000000400856 call $+5
.text:000000000040085B
.text:000000000040085B L0_64:
.text:000000000040085B pop rax
.text:000000000040085C add rax, 24h
.text:0000000000400860 xor rcx, rcx
.text:0000000000400863 mov dl, [rsi+rcx]
.text:0000000000400866
.text:0000000000400866 L1_64: ; CODE XREF: .text:0000000000400878↓j
.text:0000000000400866 ; .text:000000000040087D↓j
.text:0000000000400866 mov bl, [rax+rcx]
.text:0000000000400869 xor bl, dl
.text:000000000040086B mov [rax+rcx], bl
.text:000000000040086E mov dh, [rax+rcx-1]
.text:0000000000400872 inc rcx
.text:0000000000400875 cmp dh, 0FBh
.text:0000000000400878 jz short L1_64
.text:000000000040087A cmp bl, 90h
.text:000000000040087D jnz short L1_64
.text:000000000040087F sub eax, 2D6D61E8h
.text:0000000000400884 db 48h
.text:0000000000400884 in rax, 65h
.text:0000000000400884 ; ---------------------------------------------------------------------------
.text:0000000000400887 db 65h ; e
.text:0000000000400888 db 65h, 2Dh, 54h, 0ACh
.text:000000000040088C LEn0_64 dd 556D79EFh
.text:0000000000400890 dq 2DA49A2D6D79EDB6h, 0E5602D8A19459CE6h, 71EFAC542D656565h
1)程序调用mprotect,让代码段0x400000 开始大小为0x1000可读可写可执行。(这里面就存在一个漏洞,代码可以写了)
text:00000000004007E9 call _setbuf
.text:00000000004007EE mov rax, cs:stdout@@GLIBC_2_2_5
.text:00000000004007F5 mov esi, 0 ; buf
.text:00000000004007FA mov rdi, rax ; stream
.text:00000000004007FD call _setbuf
.text:0000000000400802 mov eax, 0
.text:0000000000400807 call welcome
//花指令
.text:000000000040080C call $+5
.text:0000000000400811
.text:0000000000400811 L_64:
.text:0000000000400811 pop rax
.text:0000000000400812 add rax, 7
.text:0000000000400816 jmp rax
.text:0000000000400816 main endp
.text:0000000000400816
.text:0000000000400818 ; ---------------------------------------------------------------------------
//调用mprotect将0x400000 大小0x1000属性改成可读可写可执行
.text:0000000000400818 sar rax, 0Ch
.text:000000000040081C shl rax, 0Ch
.text:0000000000400820 mov rdi, rax
.text:0000000000400823 mov rdx, 7
.text:000000000040082A mov rax, 0Ah
.text:0000000000400831 mov rsi, 1000h
.text:0000000000400838 syscall ; LINUX - sys_mprotect
//从终端读取6个字符到全局变量szCh中
.text:000000000040083A xor rax, rax
.text:000000000040083D mov rdx, 6
.text:0000000000400844 push rax
.text:0000000000400845 lea rax, szCh
.text:000000000040084D mov rsi, rax
.text:0000000000400850 pop rax
.text:0000000000400851 mov rdi, rax
.text:0000000000400854 syscall ; LINUX -
//使用输入的第一个字符,与40087F地址开始进行亦或,并写回到40087F相关内容中
//直到遇到亦或的结果为0x90结束。
.text:0000000000400856 call $+5
.text:000000000040085B
.text:000000000040085B L0_64:
.text:000000000040085B pop rax
.text:000000000040085C add rax, 24h
.text:0000000000400860 xor rcx, rcx
.text:0000000000400863 mov dl, [rsi+rcx]
.text:0000000000400866
.text:0000000000400866 L1_64: ; CODE XREF: .text:0000000000400878↓j
.text:0000000000400866 ; .text:000000000040087D↓j
.text:0000000000400866 mov bl, [rax+rcx]
.text:0000000000400869 xor bl, dl
.text:000000000040086B mov [rax+rcx], bl
.text:000000000040086E mov dh, [rax+rcx-1]
.text:0000000000400872 inc rcx
.text:0000000000400875 cmp dh, 0FBh
.text:0000000000400878 jz short L1_64
.text:000000000040087A cmp bl, 90h
.text:000000000040087D jnz short L1_64
.text:000000000040087F sub eax, 2D6D61E8h
.text:0000000000400884 db 48h
.text:0000000000400884 in rax, 65h
.text:0000000000400884 ; ---------------------------------------------------------------------------
.text:0000000000400887 db 65h ; e
.text:0000000000400888 db 65h, 2Dh, 54h, 0ACh
.text:000000000040088C LEn0_64 dd 556D79EFh
.text:0000000000400890 dq 2DA49A2D6D79EDB6h, 0E5602D8A19459CE6h, 71EFAC542D656565h
1)程序调用mprotect,让代码段0x400000 开始大小为0x1000可读可写可执行。(这里面就存在一个漏洞,代码可以写了)
2)从终端读取6个字符到scZh全局变量中。
3)使用scZh的第一个字符对代码段40087F开始进行亦或解密。
4)解密终止条件是遇到亦或后的数据为0x90(nop指令)
从上面分析来看程序存在SMC,算法为亦或算法,秘钥为用户输入。这里就要猜测秘钥是什么,由于是亦或算法,比较好猜测,我们知道正常的汇编指令都存在类似如下的指令:
.text:0000000000400823 48 C7 C2 07 00 00 00 mov rdx, 7
.text:000000000040082A 48 C7 C0 0A 00 00 00 mov rax, 0Ah
.text:0000000000400831 48 C7 C6 00 10 00 00 mov rsi, 1000h
.text:0000000000400823 48 C7 C2 07 00 00 00 mov rdx, 7
.text:000000000040082A 48 C7 C0 0A 00 00 00 mov rax, 0Ah
.text:0000000000400831 48 C7 C6 00 10 00 00 mov rsi, 1000h
多个0连续挨着,而任何数和0亦或就为它本身,因此我们在加密后的数据中查找连续一样的数据,就可以猜到秘钥。如下:
.text:000000000040087F 2D db 2Dh
.text:0000000000400880 E8 db 0E8h
.text:0000000000400881 61 db 61h ; a
.text:0000000000400882 6D db 6Dh ; m
.text:0000000000400883 2D db 2Dh ; -
.text:0000000000400884 48 db 48h
.text:0000000000400885 E5 db 0E5h
.text:0000000000400886 65 db 65h ; e
.text:0000000000400887 65 db 65h ; e
.text:0000000000400888 65 db 65h
.text:0000000000400889 2D db 2Dh ; -
.text:000000000040087F 2D db 2Dh
.text:0000000000400880 E8 db 0E8h
.text:0000000000400881 61 db 61h ; a
.text:0000000000400882 6D db 6Dh ; m
.text:0000000000400883 2D db 2Dh ; -
.text:0000000000400884 48 db 48h
.text:0000000000400885 E5 db 0E5h
.text:0000000000400886 65 db 65h ; e
.text:0000000000400887 65 db 65h ; e
.text:0000000000400888 65 db 65h
.text:0000000000400889 2D db 2Dh ; -
看到40886开始有3个连续的0x65,因此我们可以断定第一秘钥为0x65,我们IDA载入输入'eAAAAA'后执行解密代码后,如下:
.text:000000000040087F 48 8D 04 08 lea rax, [rax+rcx]
.text:0000000000400883 48 2D 80 00 00 00 sub rax, 80h
.text:0000000000400889 48 31 C9 xor rcx, rcx
.text:000000000040088C
.text:000000000040088C LEn0_64: ; CODE XREF: .text:000000000040089B↓j
.text:000000000040088C 8A 1C 08 mov bl, [rax+rcx]
.text:000000000040088F 30 D3 xor bl, dl
.text:0000000000400891 88 1C 08 mov [rax+rcx], bl
.text:0000000000400894 48 FF C1 inc rcx
.text:0000000000400897 48 83 F9 20 cmp rcx, 20h
.text:000000000040089B 7C EF jl short LEn0_64
.text:000000000040089D 48 05 80 00 00 00 add rax, 80h
.text:00000000004008A3 48 31 C9 xor rcx, rcx
.text:00000000004008A6 8A 14 0E mov dl, [rsi+rcx]
.text:00000000004008A9 48 FF C6 inc rsi
.text:00000000004008AC 32 14 0E xor dl, [rsi+rcx]
.text:00000000004008AF
.text:00000000004008AF L2_64: ; CODE XREF: .text:00000000004008C1↓j
.text:00000000004008AF ; .text:00000000004008C6↓j
.text:00000000004008AF 8A 1C 08 mov bl, [rax+rcx]
.text:00000000004008B2 30 D3 xor bl, dl
.text:00000000004008B4 88 1C 08 mov [rax+rcx], bl
.text:00000000004008B7 8A 74 08 FF mov dh, [rax+rcx-1]
.text:00000000004008BB 48 FF C1 inc rcx
.text:00000000004008BE 80 FE FB cmp dh, 0FBh
.text:00000000004008C1 74 EC jz short L2_64
.text:00000000004008C3 80 FB 90 cmp bl, 90h
.text:00000000004008C6 75 E7 jnz short L2_64
.text:00000000004008C8 90 nop
.text:00000000004008C9 5B pop rbx
.text:00000000004008CA 9E sahf
.text:00000000004008CA ; ---------------------------------------------------------------------------
.text:00000000004008CB 17 db 17h
.text:00000000004008CC 1B db 1Bh
.text:00000000004008CD 5B db 5Bh ; [
.text:00000000004008CE 3E db 3Eh ; >
.text:00000000004008CF 93 db 93h
可以看出来代码已经解密出来
.text:000000000040087F 48 8D 04 08 lea rax, [rax+rcx]
.text:0000000000400883 48 2D 80 00 00 00 sub rax, 80h
.text:0000000000400889 48 31 C9 xor rcx, rcx
.text:000000000040088C
.text:000000000040088C LEn0_64: ; CODE XREF: .text:000000000040089B↓j
.text:000000000040088C 8A 1C 08 mov bl, [rax+rcx]
.text:000000000040088F 30 D3 xor bl, dl
.text:0000000000400891 88 1C 08 mov [rax+rcx], bl
.text:0000000000400894 48 FF C1 inc rcx
.text:0000000000400897 48 83 F9 20 cmp rcx, 20h
.text:000000000040089B 7C EF jl short LEn0_64
.text:000000000040089D 48 05 80 00 00 00 add rax, 80h
.text:00000000004008A3 48 31 C9 xor rcx, rcx
.text:00000000004008A6 8A 14 0E mov dl, [rsi+rcx]
.text:00000000004008A9 48 FF C6 inc rsi
.text:00000000004008AC 32 14 0E xor dl, [rsi+rcx]
.text:00000000004008AF
.text:00000000004008AF L2_64: ; CODE XREF: .text:00000000004008C1↓j
.text:00000000004008AF ; .text:00000000004008C6↓j
.text:00000000004008AF 8A 1C 08 mov bl, [rax+rcx]
.text:00000000004008B2 30 D3 xor bl, dl
.text:00000000004008B4 88 1C 08 mov [rax+rcx], bl
.text:00000000004008B7 8A 74 08 FF mov dh, [rax+rcx-1]
.text:00000000004008BB 48 FF C1 inc rcx
.text:00000000004008BE 80 FE FB cmp dh, 0FBh
.text:00000000004008C1 74 EC jz short L2_64
.text:00000000004008C3 80 FB 90 cmp bl, 90h
.text:00000000004008C6 75 E7 jnz short L2_64
.text:00000000004008C8 90 nop
.text:00000000004008C9 5B pop rbx
.text:00000000004008CA 9E sahf
.text:00000000004008CA ; ---------------------------------------------------------------------------
.text:00000000004008CB 17 db 17h
.text:00000000004008CC 1B db 1Bh
.text:00000000004008CD 5B db 5Bh ; [
.text:00000000004008CE 3E db 3Eh ; >
.text:00000000004008CF 93 db 93h
可以看出来代码已经解密出来
依次类推第一次输入的秘钥必须为:“”evXnaK“,从而让代码完全解密。本程序还有技巧是,当代码执行完后又将代码进行加密,可能这样的做法是为了增加当采用更改程序流程时,增加难度。先不管这个。
写了脚本将程序对程序进行去除花指令和解密后main函数代码大概如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
int result; // eax
const char **v5; // [rsp+20h] [rbp-40h]
int v6; // [rsp+2Ch] [rbp-34h]
__int64 v7; // [rsp+30h] [rbp-30h]
__int64 v8; // [rsp+38h] [rbp-28h]
__int64 v9; // [rsp+40h] [rbp-20h]
__int64 v10; // [rsp+48h] [rbp-18h]
__int64 v11; // [rsp+50h] [rbp-10h]
unsigned __int64 v12; // [rsp+58h] [rbp-8h]
v6 = argc;
v5 = argv;
v12 = __readfsqword(0x28u);
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
welcome(v3, 0LL);
syscall; LINUX - sys_mprotect
__asm { syscall; LINUX - sys_read }
__asm { syscall; LINUX - sys_write }
fflush(0LL);
lpGoble = (char *)&v5;
__asm { syscall; LINUX - sys_read }
printf((const char *)&v5, &v5, 26LL, argv);
__asm
{
syscall; LINUX - sys_read
syscall; LINUX - sys_mprotect
}
result = 0;
JUMPOUT(__readfsqword(0x28u), v12, &locret_400ABA);
return result;
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rdi
int result; // eax
const char **v5; // [rsp+20h] [rbp-40h]
int v6; // [rsp+2Ch] [rbp-34h]
__int64 v7; // [rsp+30h] [rbp-30h]
__int64 v8; // [rsp+38h] [rbp-28h]
__int64 v9; // [rsp+40h] [rbp-20h]
__int64 v10; // [rsp+48h] [rbp-18h]
__int64 v11; // [rsp+50h] [rbp-10h]
unsigned __int64 v12; // [rsp+58h] [rbp-8h]
v6 = argc;
v5 = argv;
v12 = __readfsqword(0x28u);
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
setbuf(stdin, 0LL);
v3 = stdout;
setbuf(stdout, 0LL);
welcome(v3, 0LL);
syscall; LINUX - sys_mprotect
__asm { syscall; LINUX - sys_read }
__asm { syscall; LINUX - sys_write }
fflush(0LL);
lpGoble = (char *)&v5;
__asm { syscall; LINUX - sys_read }
printf((const char *)&v5, &v5, 26LL, argv);
__asm
{
syscall; LINUX - sys_read
syscall; LINUX - sys_mprotect
}
result = 0;
JUMPOUT(__readfsqword(0x28u), v12, &locret_400ABA);
return result;
}
四、分析真正的main函数
1、当输入
evXnaK 之后,程序将在终端输出"wow!\n"
2、再次从终端读取0x1A大小的数据到局部变量中(堆栈中)。
3、然后将读到的数据写入到终端中。
4、再次读取0x200个大小的数据到局部变量中
(堆栈中)。
5、将调用mprotect将程序改成只读只执行,不可写。
6、结束程序。
五、分析漏洞
从上面来看,存在3个漏洞:
1、将用户输入的数据存储在局部变量中,并且调用printf打印出来,利用printf格式化字符串漏洞可以实现任意地址读写(百度告诉的,之前不知道哈)
2、读取0x200个数据到堆栈中,如果用户输入0x200个数据,明显会造成堆栈溢出。可以使用缓冲区溢出漏洞进行攻击。(这个还是了解的)
3、 本题为了实现SMC,必须调用mprotect对代码进行解密,程序最后也将代码改成不可写了,但是调用mprotect的时机晚了,其在main函数结束后才调用。
但是调用printf时可以对任意地址读写的。如果将代码解密后再立即调用mprotect这个题难度可能会更高。
4、调用checksec wow看下程序的保护机制 如下:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以看 .plt.got段可读写 ,同时没有开启pie,表示程序代码段地址不变。但是开启了堆栈检测,并且堆栈不可执行。
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以看 .plt.got段可读写 ,同时没有开启pie,表示程序代码段地址不变。但是开启了堆栈检测,并且堆栈不可执行。
6、如何利用漏洞?
程序在在调用printf时,代码段是可以写的,因此我们可以使用printf漏洞更改程序的执行流程,让代码一致进行如下循环:
while(1)
{
read(0, buf1,0x1a)
printf(buf1)
read(0,buf2,0x200)
}
while(1)
{
read(0, buf1,0x1a)
printf(buf1)
read(0,buf2,0x200)
}
起始可以将最后的read(0, buf2,0x200)也去掉。
相关对应的汇编代码如下:
.text:0000000000400A30 48 31 C0 xor rax, rax
.text:0000000000400A33
.text:0000000000400A33 loc_400A33: ; CODE XREF: .text:L_J0↑j
.text:0000000000400A33 48 C7 C2 1A 00 00 00 mov rdx, 1Ah
.text:0000000000400A3A 48 89 E6 mov rsi, rsp
.text:0000000000400A3D 48 89 24 25 88 10 60 00 mov ds:qword_601088, rsp
.text:0000000000400A45 48 89 C7 mov rdi, rax
.text:0000000000400A48 0F 05 syscall ; LINUX - sys_read
.text:0000000000400A4A 48 8B 05 37 06 20 00 mov rax, cs:qword_601088
.text:0000000000400A51 48 89 C7 mov rdi, rax
.text:0000000000400A54 B8 00 00 00 00 mov eax, 0
.text:0000000000400A59 E8 52 FB FF FF call sub_4005B0
.text:0000000000400A5E 48 31 C0 xor rax, rax
.text:0000000000400A61 48 C7 C2 00 02 00 00 mov rdx, 200h
.text:0000000000400A68 48 8D 74 24 E0 lea rsi, [rsp-20h]
.text:0000000000400A6D 48 89 C7 mov rdi, rax
.text:0000000000400A70 0F 05 syscall ; LINUX - sys_read
.text:0000000000400A72 90 nop
.text:0000000000400A73 E8 00 00 00 00 call $+5
.text:0000000000400A30 48 31 C0 xor rax, rax
.text:0000000000400A33
.text:0000000000400A33 loc_400A33: ; CODE XREF: .text:L_J0↑j
.text:0000000000400A33 48 C7 C2 1A 00 00 00 mov rdx, 1Ah
.text:0000000000400A3A 48 89 E6 mov rsi, rsp
.text:0000000000400A3D 48 89 24 25 88 10 60 00 mov ds:qword_601088, rsp
.text:0000000000400A45 48 89 C7 mov rdi, rax
.text:0000000000400A48 0F 05 syscall ; LINUX - sys_read
.text:0000000000400A4A 48 8B 05 37 06 20 00 mov rax, cs:qword_601088
.text:0000000000400A51 48 89 C7 mov rdi, rax
.text:0000000000400A54 B8 00 00 00 00 mov eax, 0
.text:0000000000400A59 E8 52 FB FF FF call sub_4005B0
.text:0000000000400A5E 48 31 C0 xor rax, rax
.text:0000000000400A61 48 C7 C2 00 02 00 00 mov rdx, 200h
.text:0000000000400A68 48 8D 74 24 E0 lea rsi, [rsp-20h]
.text:0000000000400A6D 48 89 C7 mov rdi, rax
.text:0000000000400A70 0F 05 syscall ; LINUX - sys_read
.text:0000000000400A72 90 nop
.text:0000000000400A73 E8 00 00 00 00 call $+5
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2018-6-22 11:49
被oooAooo编辑
,原因: