HackThisSite - Application Mission 5

Challenge

An application stores its password on the stack and compares it against user input. 应用程序要求输入密码进行验证,正确密码以常量的形式写在函数内部,运行时复制到栈上,再与用户输入逐字节比较。

附件含两个版本:app5win.zip(Windows 控制台 exe Live_Application_5.exe)和 app5unix.tar.gz(ELF 可执行文件 app5unix)。官方暗示两个平台的密码一致,Linux 版没有 strip,直接从本地 ELF 入手最快。

Solution

Recon:

  • file app5unixELF 32-bit LSB pie executable, Intel i386, not stripped,源码文件名残留在符号表里:app5win.c(Linux 版由同一份 C 源码编译)。
  • strings -a app5unix 里看不到完整密码,但有 4 个可疑短串 powertrippin,以及 Please enter the password:Invalid PasswordThe password is %s
  • powe / rtri / ppin 恰好是 4 字节对齐的 ASCII 片段 —— 它们是被拆成 4 个 dword、以立即数形式 mov 进栈的常量,反汇编后按写入顺序拼回来即可。

Step 1: 反汇编 main,定位常量与比较循环

函数符号没去掉,直接反汇编 main

1
$ objdump -d -M intel app5unix --section=.text | sed -n '/<main>:/,/^$/p'

main 的完整反汇编如下(含 PIE 序言与栈保护样板):

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
124d:  f3 0f 1e fb           endbr32
1251: 8d 4c 24 04 lea ecx,[esp+0x4]
1255: 83 e4 f0 and esp,0xfffffff0
1258: ff 71 fc push DWORD PTR [ecx-0x4]
125b: 55 push ebp
125c: 89 e5 mov ebp,esp
125e: 53 push ebx
125f: 51 push ecx
1260: 83 ec 50 sub esp,0x50
1263: e8 e8 fe ff ff call 1150 <__x86.get_pc_thunk.bx>
1268: 81 c3 60 2d 00 00 add ebx,0x2d60
126e: 89 c8 mov eax,ecx
1270: 8b 40 04 mov eax,DWORD PTR [eax+0x4]
1273: 89 45 b4 mov DWORD PTR [ebp-0x4c],eax
1276: 65 a1 14 00 00 00 mov eax,gs:0x14 ; stack canary
127c: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
127f: 31 c0 xor eax,eax
1281: c7 45 d4 67 0a 00 00 mov DWORD PTR [ebp-0x2c],0xa67 ; [ebp-0x2c] = 0xa67 -> 67 0a 00 00 ('g' '\n' 0 0)
1288: c7 45 d8 70 70 69 6e mov DWORD PTR [ebp-0x28],0x6e697070 ; [ebp-0x28] = "ppin"
128f: c7 45 dc 72 74 72 69 mov DWORD PTR [ebp-0x24],0x69727472 ; [ebp-0x24] = "rtri"
1296: c7 45 e0 70 6f 77 65 mov DWORD PTR [ebp-0x20],0x65776f70 ; [ebp-0x20] = "powe"
129d: 83 ec 0c sub esp,0xc
12a0: 8d 83 40 e0 ff ff lea eax,[ebx-0x1fc0]
12a6: 50 push eax
12a7: e8 34 fe ff ff call 10e0 <puts@plt>
12ac: 83 c4 10 add esp,0x10
12af: 83 ec 04 sub esp,0x4
12b2: 6a 10 push 0x10 ; memset(input, 0, 16), input 在 [ebp-0x1c]
12b4: 6a 00 push 0x0
12b6: 8d 45 e4 lea eax,[ebp-0x1c]
12b9: 50 push eax
12ba: e8 41 fe ff ff call 1100 <memset@plt>
12bf: 83 c4 10 add esp,0x10
12c2: c7 45 cc 00 00 00 00 mov DWORD PTR [ebp-0x34],0x0
12c9: c7 45 c8 00 00 00 00 mov DWORD PTR [ebp-0x38],0x0
12d0: c7 45 c4 00 00 00 00 mov DWORD PTR [ebp-0x3c],0x0
12d7: e8 e4 fd ff ff call 10c0 <getchar@plt> ; c = getchar()
12dc: 88 45 c3 mov BYTE PTR [ebp-0x3d],al
12df: 8b 45 cc mov eax,DWORD PTR [ebp-0x34]
12e2: 8d 50 01 lea edx,[eax+0x1]
12e5: 89 55 cc mov DWORD PTR [ebp-0x34],edx
12e8: 0f b6 55 c3 movzx edx,BYTE PTR [ebp-0x3d]
12ec: 88 54 05 e4 mov BYTE PTR [ebp+eax*1-0x1c],dl
12f0: 80 7d c3 0a cmp BYTE PTR [ebp-0x3d],0xa
12f4: 74 0c je 1302 <main+0xb5>
12f6: 80 7d c3 00 cmp BYTE PTR [ebp-0x3d],0x0
12fa: 74 06 je 1302 <main+0xb5>
12fc: 83 7d cc 0f cmp DWORD PTR [ebp-0x34],0xf ; len < 16 才继续
1300: 76 d5 jbe 12d7 <main+0x8a>
1302: 8d 45 e4 lea eax,[ebp-0x1c]
1305: 89 45 d0 mov DWORD PTR [ebp-0x30],eax
1308: c7 45 c8 00 00 00 00 mov DWORD PTR [ebp-0x38],0x0 ; i = 0
130f: c7 45 c4 03 00 00 00 mov DWORD PTR [ebp-0x3c],0x3 ; j = 3
1316: eb 40 jmp 1358 <main+0x10b>
1318: 8b 45 c8 mov eax,DWORD PTR [ebp-0x38] ; 取 input_dword[i>>2] 与 const_dword[j] 比较
131b: c1 e8 02 shr eax,0x2
131e: 8d 14 85 00 00 00 00 lea edx,[eax*4+0x0]
1325: 8b 45 d0 mov eax,DWORD PTR [ebp-0x30]
1328: 01 d0 add eax,edx
132a: 8b 10 mov edx,DWORD PTR [eax]
132c: 8b 45 c4 mov eax,DWORD PTR [ebp-0x3c]
132f: 8b 44 85 d4 mov eax,DWORD PTR [ebp+eax*4-0x2c]
1333: 39 c2 cmp edx,eax
1335: 74 19 je 1350 <main+0x103>
1337: 83 ec 0c sub esp,0xc ; 不等 -> printf("Invalid Password")
133a: 8d 83 5b e0 ff ff lea eax,[ebx-0x1fa5]
1340: 50 push eax
1341: e8 6a fd ff ff call 10b0 <printf@plt>
1346: 83 c4 10 add esp,0x10
1349: b8 00 00 00 00 mov eax,0x0
134e: eb 29 jmp 1379 <main+0x12c>
1350: 83 45 c8 04 add DWORD PTR [ebp-0x38],0x4 ; i += 4
1354: 83 6d c4 01 sub DWORD PTR [ebp-0x3c],0x1 ; j -= 1
1358: 83 7d c8 0c cmp DWORD PTR [ebp-0x38],0xc ; while (i <= 12)
135c: 76 ba jbe 1318 <main+0xcb>
135e: 83 ec 08 sub esp,0x8 ; 全等 -> printf("The password is %s", input)
1361: 8d 45 e4 lea eax,[ebp-0x1c]
1364: 50 push eax
1365: 8d 83 6c e0 ff ff lea eax,[ebx-0x1f94]
136b: 50 push eax
136c: e8 3f fd ff ff call 10b0 <printf@plt>
1371: 83 c4 10 add esp,0x10
1374: b8 00 00 00 00 mov eax,0x0
1379: 8b 4d f4 mov ecx,DWORD PTR [ebp-0xc]
137c: 65 33 0d 14 00 00 00 xor ecx,DWORD PTR gs:0x14
1383: 74 05 je 138a <main+0x13d>
1385: e8 96 00 00 00 call 1420 <__stack_chk_fail_local>
138a: 8d 65 f8 lea esp,[ebp-0x8]
138d: 59 pop ecx
138e: 5b pop ebx
138f: 5d pop ebp
1390: 8d 61 fc lea esp,[ecx-0x4]
1393: c3 ret

0x1281~0x1296 把 16 字节的正确密码常量分 4 个 dword 写进 [ebp-0x2c]..[ebp-0x20]0x12d7 起是一个带长度上限的 getchar 循环,把每个字符写进 [ebp-0x1c + i],遇到 \n0xa)、NUL 或长度超过 15 就停;0x1318~0x135c 是比较循环,i 从 0 每次 +4、j 从 3 每次 -1。

Step 2: 从校验循环逆推密码

观察 → 推理:

  • 常量区在栈上是 [ebp-0x2c] = 0xa67[ebp-0x28] = "ppin"[ebp-0x24] = "rtri"[ebp-0x20] = "powe"
  • 循环让 i 从 0 递增、j 从 3 递减,比较的是 input_dword[i/4] == const_dword[j]。也就是说输入的第 0/1/2/3 个 dword 要分别等于常量里第 3/2/1/0 个 dword —— 常量在内存里是倒序存的。
  • 把 4 个 dword 按内存顺序还原成字节:67 0a 00 00 | 70 70 69 6e | 72 74 72 69 | 70 6f 77 65 → 反过来按 dword 拼接(powe + rtri + ppin + g)得到 powertripping
  • 那个 0x0a 正是结尾换行 —— getchar 循环会连同 \n 一起读进 input[13],第 4 个 dword 比较时也把它纳入了匹配,所以输入 powertripping\n 能对上。

用 gdb 在常量写完、比较开始前断下,直接 dump 栈内存验证推导:

1
2
3
4
5
6
$ gdb -q -batch -ex 'set pagination off' \
-ex 'break *main+0x50' -ex 'run' \
-ex 'x/16bx $ebp-0x2c' ./app5unix
Breakpoint 1, 0x5655629d in main ()
0xffffbafc: 0x67 0x0a 0x00 0x00 0x70 0x70 0x69 0x6e
0xffffbb04: 0x72 0x74 0x72 0x69 0x70 0x6f 0x77 0x65

0xffffbafc 起 16 字节即 g \n \0 \0 p p i n r t r i p o w e,按 dword 反向读就是 powe rtri ppin g\n

Step 3: 本地运行验证

把推导出的口令喂给程序,它会自己把密码打印出来:

1
2
3
4
5
6
7
$ printf 'powertripping\n' | ./app5unix
Please enter the password:
The password is powertripping

$ printf 'wrongpass\n' | ./app5unix
Please enter the password:
Invalid Password
powertripping