目录
  1. 1. 1.不用 “ “ 输出Hello,World!
  2. 2. 2.不用 ; 输出Hello,World!
  3. 3. 3.不用# 输出Hello,World!
  4. 4. 4.不用括号输出Hello,World!(包括各种括号(),<>,{},[]都不能用 )
  5. 5. 5.对上述代码的分析
  6. 6. 6.模仿操作
对hello world的重新认识

 前段时间在逛知乎的时候,偶然发现一道有意思的编程题
知乎
参考链接:hello world编程题你会吗?
 Helllo World算是最简单也最基础的程序了,我们一般在编写c语言代码的时候,输入输出都会很自然地联想到frintf()和scanf(),这几道题非常地有意思,要求跳出常规思维输出’’Hello World’。

1.不用 “ “ 输出Hello,World!

 不用” “输出,那么就不能用scanf()这个函数了,那么怎么办呢,想到c语言在处理字符常量的时候是把它当做数字来处理的,所以可以用普通char()函数把”Hello World”的每个字符的Ascii码输出。
 这道题如果引伸为不用引号来输出(单引号和双引号都不能使用),其实也是一样的,putchar()函数可以直接用数字作为参数。

2.不用 ; 输出Hello,World!

 不用;输出,也就是出代码中不能出现完整的语句,仔细思考下在c语言当中哪些位置可以不用;的,发现if语句刚好满足要求。

附上同时满足条件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
void ()
{
if(putchar(72)){
}
if(putchar(101)){
}
if(putchar(108)){
}
if(putchar(108)){
}
if(putchar(111)){
}
if(putchar(32)){
}
if(putchar(87)){
}
if(putchar(111)){
}
if(putchar(114)){
}
if(putchar(108)){
}
if(putchar(100)){
}
}

 这里需要说明的一点是,在最新的c标准中,已经不允许main函数的类型为void。

3.不用# 输出Hello,World!

 这道题不得不感叹自己所学的知识的贫瘠,一直想着怎么在windows编译环境下怎么达到这个条件,看了原帖的回复,说是要重新定义printf()函数,然后在dev里试了试重新定义了一下printf函数,发现报错没有定义printf()这个函数。
 后来才发现原来重新定义printf()函数在linux环境下是可以实现要求的。

img

代码如下:

1
2
3
4
5
6
int printf(const char *format,...);

int ()
{
return printf("hello, world");
}

4.不用括号输出Hello,World!(包括各种括号(),<>,{},[]都不能用 )

第四题出题人并不是为了输出”Hello World”出的题,本意在了解ELF文件格式。
附上知乎大佬的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
const char main = 0x55, main1 = 0x48, main2 = 0x89, main3 = 0xe5, main4 = 0xb8,
main5 = 0x01, main6 = 0x00, main7 = 0x00, main8 = 0x00, main9 = 0xbb,
main10 = 0x01, main11 = 0x00, main12 = 0x00, main13 = 0x00,
main14 = 0x67, main15 = 0x8d, main16 = 0x35, main17 = 0x10,
main18 = 0x00, main19 = 0x00, main20 = 0x00, main21 = 0xba,
main22 = 0x0d, main23 = 0x00, main24 = 0x00, main25 = 0x00,
main26 = 0x0f, main27 = 0x05, main28 = 0xb8, main29 = 0x3c,
main30 = 0x00, main31 = 0x00, main32 = 0x00, main33 = 0x31,
main34 = 0xdb, main35 = 0x0f, main36 = 0x05, main37 = 0x48,
main38 = 0x65, main39 = 0x6c, main40 = 0x6c, main41 = 0x6f,
main42 = 0x20, main43 = 0x57, main44 = 0x6f, main45 = 0x72,
main46 = 0x6c, main47 = 0x64, main48 = 0x21, main49 = 0x0a,
main50 = 0x5d;

5.对上述代码的分析

其中最让我不能理解的是第四种方式输出的Hello ,World!

我用gcc编译器,把他编译后运行,的确能输出Hello ,World!

1
2
3
➜   gcc test.c -o test
➜ ./test
Hello World!

用ida反编译一下,可以看到start函数还是很正常的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00000000004003E0 _start          proc near               ; DATA XREF: LOAD:0000000000400018↑o
.text:00000000004003E0 ; __unwind {
.text:00000000004003E0 xor ebp, ebp
.text:00000000004003E2 mov r9, rdx ; rtld_fini
.text:00000000004003E5 pop rsi ; argc
.text:00000000004003E6 mov rdx, rsp ; ubp_av
.text:00000000004003E9 and rsp, 0FFFFFFFFFFFFFFF0h
.text:00000000004003ED push rax
.text:00000000004003EE push rsp ; stack_end
.text:00000000004003EF mov r8, offset __libc_csu_fini ; fini
.text:00000000004003F6 mov rcx, offset __libc_csu_init ; init
.text:00000000004003FD mov rdi, offset main ; main
.text:0000000000400404 call ___libc_start_main
.text:0000000000400409 hlt
.text:0000000000400409 ; } // starts at 4003E0
.text:0000000000400409 _start endp
.text:0000000000400409

继续看main函数,就变得很不正常了,而且他是在data段。???

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
.rodata:0000000000400564 ; int __cdecl main(int argc, const char **argv, const char **envp)
.rodata:0000000000400564 main db 55h ; U ; DATA XREF: _start+1D↑o
.rodata:0000000000400565 public main1
.rodata:0000000000400565 main1 db 48h ; H
.rodata:0000000000400566 public main2
.rodata:0000000000400566 main2 db 89h
.rodata:0000000000400567 public main3
.rodata:0000000000400567 main3 db 0E5h
.rodata:0000000000400568 public main4
.rodata:0000000000400568 main4 db 0B8h
.rodata:0000000000400569 public main5
.rodata:0000000000400569 main5 db 1
.rodata:000000000040056A public main6
.rodata:000000000040056A main6 db 0
.rodata:000000000040056B public main7
.rodata:000000000040056B main7 db 0
.rodata:000000000040056C public main8
.rodata:000000000040056C main8 db 0
.rodata:000000000040056D public main9
.rodata:000000000040056D main9 db 0BBh
.rodata:000000000040056E public main10
.rodata:000000000040056E main10 db 1
.rodata:000000000040056F public main11
.rodata:000000000040056F main11 db 0
.rodata:0000000000400570 public main12
.rodata:0000000000400570 main12 db 0
.rodata:0000000000400571 public main13
.rodata:0000000000400571 main13 db 0
.rodata:0000000000400572 public main14
.rodata:0000000000400572 main14 db 67h ; g
.rodata:0000000000400573 public main15
.rodata:0000000000400573 main15 db 8Dh
.rodata:0000000000400574 public main16
.rodata:0000000000400574 main16 db 35h ; 5
.rodata:0000000000400575 public main17
.rodata:0000000000400575 main17 db 10h
.rodata:0000000000400576 public main18
.rodata:0000000000400576 main18 db 0
.rodata:0000000000400577 public main19
.rodata:0000000000400577 main19 db 0
.rodata:0000000000400578 public main20
.rodata:0000000000400578 main20 db 0
.rodata:0000000000400579 public main21
.rodata:0000000000400579 main21 db 0BAh
.rodata:000000000040057A public main22
.rodata:000000000040057A main22 db 0Dh
.rodata:000000000040057B public main23
.rodata:000000000040057B main23 db 0
.rodata:000000000040057C public main24
.rodata:000000000040057C main24 db 0
.rodata:000000000040057D public main25
.rodata:000000000040057D main25 db 0
.rodata:000000000040057E public main26
.rodata:000000000040057E main26 db 0Fh
.rodata:000000000040057F public main27
.rodata:000000000040057F main27 db 5
.rodata:0000000000400580 public main28
.rodata:0000000000400580 main28 db 0B8h
.rodata:0000000000400581 public main29
.rodata:0000000000400581 main29 db 3Ch ; <
.rodata:0000000000400582 public main30
.rodata:0000000000400582 main30 db 0
.rodata:0000000000400583 public main31
.rodata:0000000000400583 main31 db 0
.rodata:0000000000400584 public main32
.rodata:0000000000400584 main32 db 0
.rodata:0000000000400585 public main33
.rodata:0000000000400585 main33 db 31h ; 1
.rodata:0000000000400586 public main34
.rodata:0000000000400586 main34 db 0DBh
.rodata:0000000000400587 public main35
.rodata:0000000000400587 main35 db 0Fh
.rodata:0000000000400588 public main36
.rodata:0000000000400588 main36 db 5
.rodata:0000000000400589 public main37
.rodata:0000000000400589 main37 db 48h ; H
.rodata:000000000040058A public main38
.rodata:000000000040058A main38 db 65h ; e
.rodata:000000000040058B public main39
.rodata:000000000040058B main39 db 6Ch ; l
.rodata:000000000040058C public main40
.rodata:000000000040058C main40 db 6Ch ; l
.rodata:000000000040058D public main41
.rodata:000000000040058D main41 db 6Fh ; o
.rodata:000000000040058E public main42
.rodata:000000000040058E main42 db 20h
.rodata:000000000040058F public main43
.rodata:000000000040058F main43 db 57h ; W
.rodata:0000000000400590 public main44
.rodata:0000000000400590 main44 db 6Fh ; o
.rodata:0000000000400591 public main45
.rodata:0000000000400591 main45 db 72h ; r
.rodata:0000000000400592 public main46
.rodata:0000000000400592 main46 db 6Ch ; l
.rodata:0000000000400593 public main47
.rodata:0000000000400593 main47 db 64h ; d
.rodata:0000000000400594 public main48
.rodata:0000000000400594 main48 db 21h ; !
.rodata:0000000000400595 public main49
.rodata:0000000000400595 main49 db 0Ah
.rodata:0000000000400596 public main50
.rodata:0000000000400596 main50 db 5Dh ; ]
.rodata:0000000000400596 _rodata ends

接下来我用gdb动态调试一波,下断点到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
pwndbg> b *0x400564
Breakpoint 2 at 0x400564
pwndbg> c
Continuing.

Breakpoint 2, 0x0000000000400564 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
RAX 0x400564 (main) ◂— push rbp
RBX 0x0
RCX 0x0
RDX 0x7fffffffde28 —▸ 0x7fffffffe1df ◂— 'XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0'
RDI 0x1
RSI 0x7fffffffde18 —▸ 0x7fffffffe1ac ◂— '/mnt/hgfs/ubuntu_share/help/test'
R8 0x400550 (__libc_csu_fini) ◂— ret
R9 0x7ffff7de7ac0 (_dl_fini) ◂— push rbp
R10 0x846
R11 0x7ffff7a2d740 (__libc_start_main) ◂— push r14
R12 0x4003e0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde10 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x4004e0 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdd38 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
RIP 0x400564 (main) ◂— push rbp
─────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────
► 0x400564 <main> push rbp <0x4004e0>
0x400565 <main1> mov rbp, rsp
0x400568 <main4> mov eax, 1
0x40056d <main9> mov ebx, 1
0x400572 <main14> lea esi, [eip + 0x10]
0x400579 <main21> mov edx, 0xd
0x40057e <main26> syscall
0x400580 <main28> mov eax, 0x3c
0x400585 <main33> xor ebx, ebx
0x400587 <main35> syscall
0x400589 <main37> insb byte ptr [rdi], dx
──────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdd38 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax
01:0008│ 0x7fffffffdd40 ◂— 0x1
02:0010│ 0x7fffffffdd48 —▸ 0x7fffffffde18 —▸ 0x7fffffffe1ac ◂— '/mnt/hgfs/ubuntu_share/help/test'
03:0018│ 0x7fffffffdd50 ◂— 0x1f7ffcca0
04:0020│ 0x7fffffffdd58 —▸ 0x400564 (main) ◂— push rbp
05:0028│ 0x7fffffffdd60 ◂— 0x0
06:0030│ 0x7fffffffdd68 ◂— 0x74c583ff1005bf92
07:0038│ 0x7fffffffdd70 —▸ 0x4003e0 (_start) ◂— xor ebp, ebp
────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
► f 0 400564 main
f 1 7ffff7a2d830 __libc_start_main+240
Breakpoint *0x400564
pwndbg>

看到这里我明白了,上述代码其实是一段shellcode,gcc编译器是如何识别并编译的,这里我不得而知,看来还要再看一遍程序员的自我修养啊。

分析一下生成的汇编吧。0x400572 <main14> lea esi, [eip + 0x10] 其中这段汇编让我很感兴趣,

程序动态执行的时候总是把静态的数据用这种方式[eip + 0x10]来传参

很显然里面存的是Hello world,然后syscall系统调用write函数输出,第二次syscall系统调用exit函数。

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/10s 0x400579+0x10
0x400589 <main37>: "Hello World!\n]"
0x400598: "\001\033\003;,"
0x40059e: ""
0x40059f: ""
0x4005a0: "\004"
0x4005a2: ""
0x4005a3: ""
0x4005a4: "\030\376\377\377x"
0x4005aa: ""
0x4005ab: ""

6.模仿操作

用上述方式写 execve(“/bin/sh”)

1
const char  main = 0x6a, main1 = 0x42, main2 = 0x58, main3 = 0xfe, main4 = 0xc4, main5 = 0x48, main6 = 0x99, main7 = 0x52, main8 = 0x48, main9 = 0xbf,main10 = 0x2f, main11 = 0x62, main12 = 0x69, main13 = 0x6e, main14 = 0x2f, main15 = 0x2f, main16 = 0x73, main17 = 0x68, main18 = 0x57, main19 = 0x54,main20 = 0x5e, main21 = 0x49, main22 = 0x89, main23 = 0xd0, main24 = 0x49, main25 = 0x89, main26 = 0xd2, main27 = 0x0f, main28 = 0x05;

orw flag

1
const char main=0x48,main1=0xb8,main2=0x1,main3=0x1,main4=0x1,main5=0x1,main6=0x1,main7=0x1,main8=0x1,main9=0x1,main10=0x50,main11=0x48,main12=0xb8,main13=0x67,main14=0x2e,main15=0x67,main16=0x6d,main17=0x60,main18=0x66,main19=0x1,main20=0x1,main21=0x48,main22=0x31,main23=0x4,main24=0x24,main25=0x48,main26=0xb8,main27=0x2f,main28=0x68,main29=0x6f,main30=0x6d,main31=0x65,main32=0x2f,main33=0x63,main34=0x74,main35=0x50,main36=0x48,main37=0x89,main38=0xe7,main39=0x31,main40=0xd2,main41=0x31,main42=0xf6,main43=0x6a,main44=0x2,main45=0x58,main46=0xf,main47=0x5,main48=0x31,main49=0xc0,main50=0x6a,main51=0x3,main52=0x5f,main53=0x6a,main54=0x20,main55=0x5a,main56=0x48,main57=0x89,main58=0xe6,main59=0xf,main60=0x5,main61=0x6a,main62=0x1,main63=0x5f,main64=0x6a,main65=0x20,main66=0x5a,main67=0x48,main68=0x89,main69=0xe6,main70=0x6a,main71=0x1,main72=0x58,main73=0xf,main74=0x5;

参考:

有趣的”Hello World”

https://www.dazhuanlan.com/2020/03/03/5e5d97d48a85f/

文章作者: nocbtm
文章链接: https://nocbtm.github.io/2020/05/08/对hello-world的重新认识/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nocbtm's Blog
打赏
  • 微信
  • 支付宝